Vue3源码reactivity响应式篇之普通对象的代理解析

概览

基础对象的代理指的是对基础对象进行代理,使基础对象的属性可以被响应式地访问和修改。该实现具体参见
packages\reactivity\src\baseHandlers.ts

本文主要介绍如下四类基础对象的代理方法:

  • mutableHandlers:可读写代理
  • readonlyHandlers:只读代理
  • shallowReactiveHandlers:浅响应式代理
  • shallowReadonlyHandlers:浅只读代理

源码分析

实际上上述四类代理方法只是实例化了两个类MutableReactiveHandlerReadonlyReactiveHandler

js 复制代码
const mutableHandlers = /* @__PURE__ */ new MutableReactiveHandler();
const readonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler();
const shallowReactiveHandlers = /* @__PURE__ */ new MutableReactiveHandler(true);
const shallowReadonlyHandlers = /* @__PURE__ */ new ReadonlyReactiveHandler(true);

MutableReactiveHandlerReadonlyReactiveHandler都是继承于BaseReactiveHandler类的,BaseReactiveHandler类的主要功能是定义基础的代理方法,而MutableReactiveHandlerReadonlyReactiveHandler类则是在基础的代理方法上进行了扩展,添加了可读写和只读的功能。

BaseReactiveHandler

BaseReactiveHandler类的实现如下:

js 复制代码
function hasOwnProperty(key) {
  if (!isSymbol(key)) key = String(key);
  const obj = toRaw(this);
  // 依赖收集
  track(obj, "has", key);
  return obj.hasOwnProperty(key);
}

class BaseReactiveHandler {
  constructor(_isReadonly = false, _isShallow = false) {
    this._isReadonly = _isReadonly;
    this._isShallow = _isShallow;
  }
  get(target, key, receiver) {
    if (key === "__v_skip") return target["__v_skip"];
    const isReadonly2 = this._isReadonly, isShallow2 = this._isShallow;
    if (key === "__v_isReactive") {
      return !isReadonly2;
    } else if (key === "__v_isReadonly") {
      return isReadonly2;
    } else if (key === "__v_isShallow") {
      return isShallow2;
    } else if (key === "__v_raw") {
      if (receiver === (isReadonly2 ? isShallow2 ? shallowReadonlyMap : readonlyMap : isShallow2 ? shallowReactiveMap : reactiveMap).get(target) || // receiver is not the reactive proxy, but has the same prototype
      // this means the reciever is a user proxy of the reactive proxy
      Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)) {
        return target;
      }
      return;
    }
    const targetIsArray = isArray(target);
    if (!isReadonly2) {
      let fn;
      if (targetIsArray && (fn = arrayInstrumentations[key])) {
        return fn;
      }
      if (key === "hasOwnProperty") {
        return hasOwnProperty;
      }
    }
    const res = Reflect.get(target, key, isRef(target) ? target : receiver);
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res;
    }
    if (!isReadonly2) {
      track(target, "get", key);
    }
    if (isShallow2) {
      return res;
    }
    if (isRef(res)) {
      return targetIsArray && isIntegerKey(key) ? res : res.value;
    }
    if (isObject(res)) {
      return isReadonly2 ? readonly(res) : reactive(res);
    }
    return res;
  }
}

BaseReactiveHandler类只是定义了get()方法,当读取代理对象时,会触发get()方法进行拦截,在非只读的情况下,会调用track方法进行依赖收集。如下是BaseReactiveHandler类的实现过程分析:

  1. 类的构造器接收两个参数:_isReadonly_isShallow,分别表示是否只读和是否是浅层响应式.
  2. get()方法接收三个参数:target目标对象、key属性名、receiver代理对象。当get()方法被触发时,若key__v_isReactive__v_isShallow或者__v_raw,则根据实例的_isReadonly或者_isShallow返回对应的布尔值.
  3. key__v_raw,则根据_isReadonly_isShallow确定代理对象的缓存(即调用createReactiveObject中的proxyMap缓存变量),然后比较receiver是否是缓存中的代理对象,如果是,则返回目标对象target;若不是,则判断targetreceiver的原型是否相等,若相等,则返回目标对象target.如果二者皆不相等,则什么也不返回.
  4. key不是上述属性,则判断target是否是数组。如果是可写的,且target是数组,key又是数组的属性,那么就返回数组的方法。若keyhasOwnProperty,则返回自定义的hasOwnProperty方法,该方法的实现如上,该方法的作用是判断target是否有key属性,并且调用track进行依赖收集.
  5. 调用Reflect.get(target, key, receiver)方法获取targetkey的值res.
  6. 判断key是不是内置的Symbol属性或一些普通属性__proto__,__v_isRef,__isVue,若是,则直接返回res,避免对这些属性进行依赖收集.
  7. 判断是否只读,若不是只读,则调用track进行依赖收集.
  8. 判断是否是浅层响应式,若是,则直接返回res,避免对res进行递归代理.
  9. 判断res是否是Ref对象,若是,则继续判断,若target是数组且key是整数索引,则返回res;否则返回res.value.
  10. res是对象,则判断是否只读,若不是只读,则递归调用reactive进行代理,否则递归调用readonly进行代理.
  11. 最后返回res.

MutableReactiveHandler

MutableReactiveHandler的实现如下:

js 复制代码
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow2 = false) {
    super(false, isShallow2);
  }
  set(target, key, value, receiver) {
    let oldValue = target[key];
    if (!this._isShallow) {
      const isOldValueReadonly = isReadonly(oldValue);
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue);
        value = toRaw(value);
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false;
        } else {
          oldValue.value = value;
          return true;
        }
      }
    }
    const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, "add", key, value);
      } else if (hasChanged(value, oldValue)) {
        trigger(target, "set", key, value, oldValue);
      }
    }
    return result;
  }
  deleteProperty(target, key) {
    const hadKey = hasOwn(target, key);
    const oldValue = target[key];
    const result = Reflect.deleteProperty(target, key);
    if (result && hadKey) {
      trigger(target, "delete", key, void 0, oldValue);
    }
    return result;
  }
  has(target, key) {
    const result = Reflect.has(target, key);
    if (!isSymbol(key) || !builtInSymbols.has(key)) {
      track(target, "has", key);
    }
    return result;
  }
  ownKeys(target) {
    track(
      target,
      "iterate",
      isArray(target) ? "length" : ITERATE_KEY
    );
    return Reflect.ownKeys(target);
  }
}

MutableReactiveHandler类继承于BaseReactiveHandler类,在其内部定义了setdeletePropertyhasownKeys方法四个方法。在它的构造函数中,接受一个参数isShallow2表示是否是浅层响应,默认为false,然后调用super,响应式对象肯定不是只读的,所以super第一个参数是false。如下分析MutableReactiveHandler的四个方法。

set

target目标对象上的值发生改变,或者说是对target进行写操作时,会调用set方法。

set方法接收四个参数:target目标对象、key属性名、value属性值、receiver代理对象。

  1. 首先获取target的旧值oldValue,判断是否是浅层代理,若不是,则继续判断新值,若新值是深层响应式,则分别调用toRaw获取新值和旧值的原始值;然后判断,若target是对象,且旧值是Ref对象而新值不是Ref对象,若旧值是只读的,则返回false;否则将旧值的value属性赋值为新值,返回true.
  2. target不是数组,且key是整数索引且key的长度小于target数组的长度,则说明是在更新target[key]的值;若target不是数组,则调用自定义方法hasOwn(实际就是Object.prototype.hasOwnProperty)判断target是否有key属性.
    hadKey用于表示是更新还是新增操作,true则更新/false则新增.
  3. 调用Reflect.set设置targetkeyvalue,返回结果保存至变量result
  4. 判断targetreceiver的原始对象是否相等,只有相等才触发副作用。这个检查是为了避免在原型链上的属性设置时错误地触发副作用。然后判断hadKey,若hadKeytrue,则说明是更新操作,会先调用hasChanged判断新旧值是否相等,若不等,则调用trigger触发更新副作用;若hadKeyfalse,则说明是新增操作,调用trigger触发新增副作用.
  5. 最后返回result
deleteProperty

deleteProperty方法在删除target上的某属性时会被触发。

deleteProperty方法接收两个参数:target目标对象、key属性名。

  1. 调用hasOwn判断target是否有key属性,判断结果记为hadKey,获取旧值oldValue
  2. 调用Reflect.deleteProperty删除targetkey属性,返回结果保存至变量result
  3. 判断resulthadKey都为true,则调用trigger触发删除副作用。
  4. 最后返回result
has

has方法会拦截in操作符。如下是它的处理流程:

  1. 调用Reflect.has判断target是否有key属性,记为result
  2. 判断key是否是Symbol类型或者key是否是内置的Symbol,若不是,则调用track进行依赖收集.
  3. 最后返回属性检测的结果result
ownKeys

ownKeys方法会拦截Object.keysObject.getOwnPropertyNamesObject.getOwnPropertySymbolsfor...in循环。如下是它的处理流程:

  1. 调用track进行依赖收集。若target是数组,则跟踪length属性的变化;若target不是数组,则跟踪Symbol(iterator)。这样当对对象的属性添加、删除或数组长度变化时,就会触发相关的副作用函数。
  2. 最后返回目标对象的所有自有属性键

ReadonlyReactiveHandler

ReadonlyReactiveHandler类就更简单了,因为通过该类实例化的处理器方法都是针对只读对象的,所以它的setdeleteProperty方法都直接返回true,并在warn中提示目标对象是只读的。

ReadonlyReactiveHandler的实现如下:

js 复制代码
class ReadonlyReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow2 = false) {
    super(true, isShallow2);
  }
  set(target, key) {
    {
      warn(
        `Set operation on key "${String(key)}" failed: target is readonly.`,
        target
      );
    }
    return true;
  }
  deleteProperty(target, key) {
    {
      warn(
        `Delete operation on key "${String(key)}" failed: target is readonly.`,
        target
      );
    }
    return true;
  }
}