reactive 函数分析
这篇文章已经对源码做了详细的分析,下面主要分析下reactive这个函数,以及Proxy的细节
reactive函数的作用是对一个对象进行代理,返回Proxy对象。对Proxy对象进行操作时,会引起响应系统的反应。比如get操作会将Proxy和effect建立联系,set操作时,找到和Proxy关联的effect,并触发effect的执行。这是通过两个函数track和trigger实现。
Proxy
mutableHandlers
reactive函数会对传入的target类型进行判断,不同的类型生成的Proxy对象也不一样。对于Object和Array类型的target,会使用mutableHandlers生成Proxy。
mutableHandlers代理了get,has,ownKeys,set,deleteProperty这几个捕获器。修改target会触发捕获器函数的执行,进而触发track和trigger的执行。
track
track函数用来实现追踪功能,在effect函数中使用Proxy时,捕获器会识别到对Proxy的使用情况,从而实现追踪。追踪功能的核心是effect.ts文件中的targetMap对象,对象存储了所有Proxy代理的target,以及target对象上被追踪的属性、ITERATE_KEY、MAP_KEY_ITERATE_KEY,以及他们关联的effect对象。
track函数也可以追踪target上还不存在的属性,等到target上新增改属性时,就能获取到之前的追踪记录。
TrackOpTypes
track函数的第二个参数用来传递类型,源码中用一个enum TrackOpTypes类型表示,下面会详细解释每一种类型都在什么情况下被使用
export const enum TrackOpTypes {
GET = 'get',
HAS = 'has',
ITERATE = 'iterate'
}
TrackOpTypes.GET
当reactive函数传入普通对象时,get捕获器中会执行track(target, TrackOpTypes.GET, key)。在Proxy对象上执行.和[]操作、Reflect.get函数调用,都会触发get捕获器。
var p = reactive({ count: 1 });
effect(function() {
// p.count 会触发`get`捕获器的执行
// 对 count 属性的追踪
console.log(p.count);
console.log(p['count']);
console.log(Reflect.get(p, 'count'));
});
当reactive函数传入数组时,get捕获器会先在arrayInstrumentations对象上查找,如果找不到,再在代理对象target上查找。arrayInstrumentations对象会重写两类函数,一类是查询类函数includes, indexOf, lastIndexOf,代表对数组的读取操作。在这些函数中会执行track函数,对数组上的索引和length属性进行追踪。一类是修改类函数push, pop, shift, unshift, splice,代表对数组的修改操作,在这些函数中暂停了全局的追踪功能,防止某些情况下导致死循环。
var p = reactive([1, 2, 3]);
effect(function() {
// p.indexOf 会获取到 arrayInstrumentations 上重写的 indexOf函数,
// 触发对所有已存索引的追踪,和对length属性的追踪
p.includes(1);
p.indexOf(1);
p.lastIndexOf(1);
})
TrackOpTypes.HAS
Proxy的has捕获器会追踪TrackOpTypes.HAS类型的操作,追踪对应的字段。下面是has捕获器的触发条件
var p = reactive({ count: 1 });
effect(function() {
// in 操作
// 触发 TrackOpTypes.HAS 类型的追踪,追踪 count 属性
'count' in p;
// 原型链上的 in 操作
var subP = Object.create(p);
'count' in subP;
// Reflect.has 函数
Reflect.has(p, 'count');
// with 检查
with(p) {
(count);
}
});
TrackOpTypes.ITERATE
在Proxy的ownKeys捕获器中,track函数会追踪TrackOpTypes.ITERATE类型的操作,追踪的字段是length(数组)或ITERATE_KEY(对象)。表示获取对象的所有属性,或数组的所有下标。如下是捕获器的触发条件
var p = reactive({ count: 1 });
effect(function() {
// 对 ITERATE_KEY 的追踪
Object.keys(p);
Reflect.ownKeys(p);
Object.getOwnPropertyNames(p);
Object.getOwnPropertySymbols(p);
});
var parr = reactive([1, 2, 3]);
effect(function() {
// in 操作也可以触发 ownKeys
// 对 length 的追踪
for(let i in parr) {
;
};
});
TriggerOpTypes
当修改Proxy时,修改相关的捕获器会被调用,触发trigger函数执行。trigger函数会找到使用track函数追踪的属性及其关联的effect函数,然后执行关联的effect函数。trigger函数的第二个参数需要传入TriggerOpTypes类型,具体定义如下
export const enum TriggerOpTypes {
SET = 'set',
ADD = 'add',
DELETE = 'delete',
CLEAR = 'clear'
}
TriggerOpTypes.SET 和 TriggerOpTypes.ADD
在Proxy的set捕获器中,如果target不存在key属性,或者数组的索引key大于等于length,就会触发TriggerOpTypes.ADD的操作,否则触发TriggerOpTypes.SET类型
var p = reactive({ count: 1 });
effect(function () {
// 对 count 属性的追踪,响应 TriggerOpTypes.SET 操作
console.log(p.count);
// 对 ITERATE_KEY 的追踪,不响应 TriggerOpTypes.SET 操作
// 此处不响应 TriggerOpTypes.SET 操作是正常的,ITERATE_KEY 表示获取对象的所有属性,SET 操作不涉及增删属性的动作
console.log(Object.keys(p));
});
// 对 count 属性赋值,会触发 TriggerOpTypes.SET 类型
p.count = 2; // 2
effect(function () {
// 对 newprop 属性的追踪,即使 target 上还不存在 newprop 属性。响应 TriggerOpTypes.ADD 操作
console.log(p.newprop);
// 对 ITERATE_KEY 的追踪,响应 TriggerOpTypes.ADD 操作
console.log(Object.keys(p));
});
// 对 p 新增属性,会触发 TriggerOpTypes.ADD 类型
p.newprop = 'hello';
var parr = reactive([1, 2, 3]);
effect(function() {
// 对 0 下标的追踪,响应 TriggerOpTypes.SET 操作,不响应 TriggerOpTypes.ADD 操作
console.log(parr[0]);
// 对 length 的追踪,响应 TriggerOpTypes.SET 操作,响应 TriggerOpTypes.ADD 操作
console.log(parr.length);
});
// 对数组的0索引赋值, 索引0小于数组的长度,会触发 TriggerOpTypes.SET
parr[0] = 2;
// 索引3等于数组的长度,会触发 TriggerOpTypes.ADD
parr[3] = 4;
TriggerOpTypes.DELETE
Proxy的deleteProperty捕获器会触发TriggerOpTypes.DELETE类型的触发操作
var p = reactive({ count: 1 });
effect(function() {
// 对 count 属性的追踪,响应 TriggerOpTypes.DELETE 操作
console.log(p.count);
// 对 ITERATE 的追踪,响应 TriggerOpTypes.DELETE 操作
console.log(Object.keys(p));
});
// 触发 TriggerOpTypes.DELETE 类型
delete p['count']; // undefined []
// Reflect.deleteProperty(p, 'count');
var parr = reactive([0, 1, 2]);
effect(function() {
// 对 0 下标的追踪,响应 TriggerOpTypes.DELETE 操作
console.log(parr[0]);
// 对 length 的追踪,响应 TriggerOpTypes.DELETE 操作
console.log(parr.length);
for (let i in parr) { }
});
// 触发 TriggerOpTypes.DELETE 类型
delete p[0];
TriggerOpTypes.CLEAR
Map的clear函数会触发TriggerOpTypes.CLEAR的操作,由于不是mutableHandlers的功能,这里不做说明。