vue3响应式原理之proxy

169 阅读3分钟

我正在参加「掘金·启航计划」

vue3响应式原理之proxy

proxy

proxyES6提供的一种机制,用于修改目标对象的默认行为。通俗点理解,可当成拦截器,将对目标对象的某些操作进行拦截处理后再返回。

使用场景

  • vue项目中常用proxy来设置代理httpws请求等
  • vue3响应式数据的创建通过proxy实现

使用方法

创建proxy构造函数 new Proxy(target, handler)

  • target: 所要代理的目标对象
  • handler: 配置对象,传入函数,即对目标对象拦截的一些操作,操作包括以下几种

handler可拦截的操作

  • get: 3个参数,目标对象、属性名、proxy实例本身

  • set: 4个参数,目标对象、属性名、属性值、proxy实例本身(可选),应返回true,否则在严格模式没有返回true则报错

  • has: 用于拦截hasProperty(无法拦截hasOwnProperty)操作,判断对象是否有某属性;2个参数,目标对象、查询的属性名。in运算符生效,对for...in不生效;

  • deleteProperty: 用于拦截delete操作,若返回false或者抛异常,则无法删除

  • ownKeys: 拦截对象自身属性的读写操作,传入一个参数target,作用于以下方法

    1. Object.getOwnPropertyNames()
    2. Object.getOwnPropertySymbols()
    3. Object.keys()
    4. for...in
  • getOwnPropertyDescriptor: 返回属性的描述对象或undefined,2个参数,targetkey

  • defineProperty: 拦截Object.defineProperty()操作,3个参数,目标函数、定义的属性名、描述;返回Booleanfalse表示操作失败,但本身不阻止添加新属性

  • getPrototypeOf: 用于拦截对象原型,1个传参target,作用于以下操作

    1. Object.prototype.__proto__
    2. Object.prototype.isPrototypeOf()
    3. Object.getPrototypeOf()
    4. Reflect.getPrototypeOf()
    5. instanceof
  • isExtensible: 用于拦截Object.isExtensible()操作,用于定义对象是否可扩展。该方法只能返回Boolean,否则自动转为Boolean

  • preventExtensions: 拦截Object.preventExtensions(),必须返回Boolean,否则自动转为Boolean;传入一个参数target

  • setPrototypeOf: 用于拦截Object.setPrototypeOf()(设置target的原型对象),返回值只能是Boolean

  • apply: 用于拦截函数的调用、callapply操作。3个参数,目标对象、目标对象的上下文对象(this)、目标对象的参数数组(args)

var handler = {
    apply(target, ctx, args) {
        return Reflect.apply(...arguments)
    }
}
  • construct: 用于拦截new命令,3个参数,目标对象、构造函数的参数数组args、创造实例对象时,new命令作用的构造函数;必须返回一个对象,否则报错

实例

该实例是vue3源码-mini版中拿到的代码

先定义 getset 方法

const get = createGetter();
const set = createSetter();

function createGetter(isReadonly = false, shallow = false) {
  return function get(target, key, receiver) {
    const isExistInReactiveMap = () =>
      key === ReactiveFlags.RAW && receiver === reactiveMap.get(target);

    const isExistInReadonlyMap = () =>
      key === ReactiveFlags.RAW && receiver === readonlyMap.get(target);

    const isExistInShallowReadonlyMap = () =>
      key === ReactiveFlags.RAW && receiver === shallowReadonlyMap.get(target);

    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly;
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly;
    } else if (
      isExistInReactiveMap() ||
      isExistInReadonlyMap() ||
      isExistInShallowReadonlyMap()
    ) {
      return target;
    }

    const res = Reflect.get(target, key, receiver);

    // 问题:为什么是 readonly 的时候不做依赖收集呢
    // readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger
    // 所有就没有收集依赖的必要了

    if (!isReadonly) {
      // 在触发 get 的时候进行依赖收集
      track(target, "get", key);
    }

    if (shallow) {
      return res;
    }

    if (isObject(res)) {
      // 把内部所有的是 object 的值都用 reactive 包裹,变成响应式对象
      // 如果说这个 res 值是一个对象的话,那么我们需要把获取到的 res 也转换成 reactive
      // res 等于 target[key]
      return isReadonly ? readonly(res) : reactive(res);
    }

    return res;
  };
}

function createSetter() {
  return function set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver);

    // 在触发 set 的时候进行触发依赖
    trigger(target, "set", key);

    return result;
  };
}

通过new Proxy创建构造函数,通过getset代理目标对象的读写操作

const mutableHandlers = {
  get,
  set,
};
// 代理目标对象的读写方法
const proxy = new Proxy(target, baseHandlers);