Skip to content

第五章:非原始值的响应式方案

如何代理 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,当执行了一些影响数组长度的操作应触发对应的响应式

  1. 通过索引为数组设置新元素 arr[5] = 1,当数组长度小于 5 时,会隐式的修改 length 属性,所以需要触发 length 相关的副作用函数
  2. 直接修改 length 属性 arr.length = 1,其实也会隐式的修改一些元素,因此要把索引值大于 length 的元素执行其相关联副作用函数
  3. 在执行数组的 includes、indexOf、lastIndexOf 时,用户可能使用代理对象,也可能使用原对象,为了支持两种方式,需要修改查找的方法,当用户查找时先去代理对象找,找不到再去原始数组。
  4. 比如使用 push、pop、shift、unshift、splice 这些方法操作响应式数组对象时,会隐式地访问和修改数组的 length 属性,所以我们需要让这些方法间接读取 length 属性时禁止进行依赖追踪。