第五章:非原始值的响应式方案
如何代理 Object
对于一个普通对象,所有的读取方式:
- 访问属性:
obj.foo
- 判断对象或者原型是否存在 key:
key in obj
- for...in 遍历:
for(const k in obj){}
javascript
const data = { foo: 1, bar: true };
const obj = new Proxy(data, {
// 拦截 obj.foo
get(target, key) {
return target[key];
},
// 拦截 'foo' in obj
has(target, key) {
return Reflect.has(target, key);
},
//拦截 for (const k in obj) {}
ownKeys(target) {
return Reflect.ownKeys(target);
}
});
浅响应与深响应
javascript
const data = { a: 1, b: { c: 1 } };
const obj = shallowReactive(data);
const obj1 = reactive(data);
obj.b.c++; // 浅响应 c 更新后不会触发副作用函数
obj1.b.c++; // 深响应 c 更新后会触发副作用函数
javascript
// 深响应
function reactive(obj) {
return createReactive(obj);
}
// 浅响应
function shallowReactive(obj) {
return createReactive(obj, true);
}
function createReactive(obj, isShallow = false) {
return new Proxy(obj, {
get(target, key, receiver) {
// 将 activeEffect 存储的副作用函数收集到桶里
track(target, key);
const res = Reflect.get(target, key, receiver);
if (isShallow) return res;
if (typeof res === 'object' && res !== null) {
return reactive(res);
}
return res;
},
set(target, key, val) {
target[key] = val;
// 将副作用函数从 桶 中取出来并调用
trigger(target, key);
}
});
}
如何代理数组
对于一个数组,所有的读取方式:
- 索引:arr[0]
- 访问长度:arr.length
- for...in:
- for...of:
- 不修改原数组的方法:concat/join/every/some/find/findIndex/includes 等
所有的修改方式:
- 修改索引的值:arr[1] = 3
- 修改长度:arr.length = 0
- 栈方法:push、pop、shift、unshift
- 修改原数组的方法:splice、fill、sort
数组有个很特殊的属性 length
,当执行了一些影响数组长度的操作应触发对应的响应式
- 通过索引为数组设置新元素
arr[5] = 1
,当数组长度小于 5 时,会隐式的修改 length 属性,所以需要触发 length 相关的副作用函数 - 直接修改 length 属性
arr.length = 1
,其实也会隐式的修改一些元素,因此要把索引值大于 length 的元素执行其相关联副作用函数 - 在执行数组的
includes、indexOf、lastIndexOf
时,用户可能使用代理对象,也可能使用原对象,为了支持两种方式,需要修改查找的方法,当用户查找时先去代理对象找,找不到再去原始数组。 - 比如使用
push、pop、shift、unshift、splice
这些方法操作响应式数组对象时,会隐式地访问和修改数组的 length 属性,所以我们需要让这些方法间接读取 length 属性时禁止进行依赖追踪。