# 响应式原理

回顾下 2.0 响应式原理实现过程:

  1. Vue 在初始化的时候会遍历 data 数据,对于每个属性执行 defineReactive 方法,这里方法主要做以下两个事情:

    • 实例化一个事件器 Dep

    • 使用 Obejct.definedPropery() 方法代理属性并添加 setget 方法, get 中添加了收集 Watcher 的逻辑,而 set 则是包含了执行 Watcher 的逻辑

  2. 在组件渲染的时候,会实例化一个 Watcher 实例,并将当前 Watcher 实例到全局属性 Dep.target中,之后在执行渲染操作的时候会读取 data 的属性, 并被 get 劫持,get 方法中将保存在全局的 Watcher 收集到 dep

  3. 之后当 data 属性被更改时,会被 set 劫持,然后执行 Watcher 中的更新方法

# Vue3.0

3.0 中改用了 Proxy 方法,以 data 属性为例来品一下怎么玩的

源码追踪: mountComponent(n2, container) => setupComponent(instance) => setupStatefulComponent() => finishComponentSetup(instance) => applyOptions(instance, Component) => resolveData(instance, dataOptions, publicThis)

resolveData 就是处理 data 属性的地方

  function resolveData(instance, dataFn, publicThis) {
    // data属性只能是函数 
    if ( !isFunction(dataFn)) {
      warn(`The data option must be a function. ` +
        `Plain object usage is no longer supported.`);
    }
    // 获取 data 的值
    const data = dataFn.call(publicThis, publicThis);
    // data函数返回值不能是 Promise
    if ( isPromise(data)) {
      warn(`data() returned a Promise - note data() cannot be async; If you ` +
        `intend to perform data fetching before component renders, use ` +
        `async setup() + <Suspense>.`);
    }
    // data函数返回值必需是对象
    if (!isObject(data)) {
      warn(`data() should return an object.`);
    }
    else if (instance.data === EMPTY_OBJ) {
      // 设置代理
      instance.data = reactive(data);
    }
    else {
      // existing data: this is a mixin or extends.
      extend(instance.data, data);
    }
  }

上文对 data 属性和返回值做了一些校验后,执行 reactive(data) 方法设置代理

  function reactive(target) {
    // if trying to observe a readonly proxy, return the readonly version.
    if (target && target["__v_isReadonly" /* IS_READONLY */]) {
      return target;
    }
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers);
  }
  // reactive 实际上执行的是 `createReactiveObject` 方法

  function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) {
    if (!isObject(target)) {
      {
        console.warn(`value cannot be made reactive: ${String(target)}`);
      }
      return target;
    }
    // target is already a Proxy, return it.
    // exception: calling readonly() on a reactive object
    if (target["__v_raw" /* RAW */] &&
      !(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {
      return target;
    }
    // target already has corresponding Proxy
   // 使用 new WeakMap() 保存当前代理
    const proxyMap = isReadonly ? readonlyMap : reactiveMap;
    
    const existingProxy = proxyMap.get(target);
    // 如果已经存在则是直接返回
    if (existingProxy) {
      return existingProxy;
    }
    // only a whitelist of value types can be observed.
    const targetType = getTargetType(target);
    if (targetType === 0 /* INVALID */) {
      return target;
    }
    const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
    proxyMap.set(target, proxy);
    return proxy;
  }

设置代理的地方: const proxy = new Proxy(target, baseHandlers), baseHandlers 就是 Proxy 的处理器对象,根据上下文找到 baseHandlers 的定义:

  const mutableHandlers = {
    get,
    set,
    deleteProperty,
    has,
    ownKeys
  }
  const get = /*#__PURE__*/ createGetter();

  function createGetter(isReadonly = false, shallow = false) {
    return function get(target, key, receiver) { // 设置 get 方法
      // 如果当前访问 __v_isReactive 返回 true
      if (key === "__v_isReactive" /* IS_REACTIVE */) {
        return !isReadonly;
      }
      // 如果当前访问 __v_isReadonly 返回 false
      else if (key === "__v_isReadonly" /* IS_READONLY */) {
        return isReadonly;
      }
      // 如果当前访问 __v_raw  从缓存中找对应的值,先不关心这个上
      else if (key === "__v_raw" /* RAW */ &&
        receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)) {
        return target;
      }
      // 如果当前访问的数组中的属性,且是 ['includes', 'indexOf', 'lastIndexOf'] 中的一种,则调用重写的方法
      const targetIsArray = isArray(target);
      if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver);
      }
      // 获取属性对应的值
      const res = Reflect.get(target, key, receiver);
      if (isSymbol(key)
        ? builtInSymbols.has(key)
        : key === `__proto__` || key === `__v_isRef`) {
        return res;
      }
      if (!isReadonly) {
        // 执行 track 方法,收集 effect 事件方法
        track(target, "get" /* GET */, key);
      }
      if (shallow) {
        return res;
      }
      if (isRef(res)) {
        // ref unwrapping - does not apply for Array + integer key.
        const shouldUnwrap = !targetIsArray || !isIntegerKey(key);
        return shouldUnwrap ? res.value : res;
      }
      if (isObject(res)) {
        // Convert returned value into a proxy as well. we do the isObject check
        // here to avoid invalid value warning. Also need to lazy access readonly
        // and reactive here to avoid circular dependency.
        return isReadonly ? readonly(res) : reactive(res);
      }
      return res;
    };
  }
 const set = /*#__PURE__*/ createSetter()
  function createSetter(shallow = false) {
    return function set(target, key, value, receiver) {
      const oldValue = target[key]; // 得到旧的值
      if (!shallow) {
        value = toRaw(value);
        if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
          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);
      // don't trigger if target is something up in the prototype chain of original
      if (target === toRaw(receiver)) {
        if (!hadKey) {
          trigger(target, "add" /* ADD */, key, value);
        }
        else if (hasChanged(value, oldValue)) {
          // 触发收集的事件,进行组件更新
          trigger(target, "set" /* SET */, key, value, oldValue);
        }
      }
      return result;
    };
  }

# 过程分析

# 收集依赖

当组件访问 data 中的属性时被 get 方法劫持,执行下面语句:

const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
  track(target, "get" /* GET */, key);
}
// target 代理对象 属性
// type get
// key 要访问的属性
  function track(target, type, key) {
    if (!shouldTrack || activeEffect === undefined) {
      return;
    }
    // 通过当前对象获取 dep
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      // 如果没有就添加
      targetMap.set(target, (depsMap = new Map()));
    }
    // 通过属性获取 dep
    let dep = depsMap.get(key);
    if (!dep) {
      // 如果没有添加一个 dep
      depsMap.set(key, (dep = new Set()));
    }
    // 当前 dep 不包含 activeEffect 则添加到 dep 中
    if (!dep.has(activeEffect)) {
      dep.add(activeEffect);
      // 当前的dep 收集到activeEffect.deps 中
      activeEffect.deps.push(dep);
      if ( activeEffect.options.onTrack) {
        activeEffect.options.onTrack({
          effect: activeEffect,
          target,
          type,
          key
        });
      }
    }
  }

上面代码可以看到的信息:

  • 属性被访问时,会执行 track 方法做事件收集工作

  • activeEffect 是一个全局属性,记录当前正被执行的 effect, 其实这个 effect 对应 vue2.0 中的 Watcher

  • 每个代理对象都通过 WeakMap 结构保存着这个对象的 收集器集合 depsMap

  • 每个对象的属性再通过一步中的 depsMap 保存着对应的 depdepsMap 是一个 Map 结构

以上就完成的事件收集的工作

# 触发更新

当更新响应的数据时,将被 set 方法劫持, 执行 set 中的 trigger 方法

 trigger(target, "set" /* SET */, key, value, oldValue);
  function trigger(target, type, key, newValue, oldValue, oldTarget) {
    // 通过对象获取当前 收集器集合 depMap
    const depsMap = targetMap.get(target);
    if (!depsMap) { // 如果没有,说明对象没有添加响应机制
      // never been tracked
      return;
    }
    const effects = new Set(); // 保存当前收集到且要执行的 effect 
    const add = (effectsToAdd) => {
      // 将收集到的 effect 添加到 effects 集合中
      if (effectsToAdd) {
        effectsToAdd.forEach(effect => {
          if (effect !== activeEffect || effect.allowRecurse) {
            effects.add(effect);
          }
        });
      }
    };
    // 略
    if (type === "clear" /* CLEAR */) {
      // collection being cleared
      // trigger all effects for target
      depsMap.forEach(add);
    }
    // 略
    else if (key === 'length' && isArray(target)) {
      depsMap.forEach((dep, key) => {
        if (key === 'length' || key >= newValue) {
          add(dep);
        }
      });
    }
    else {
      // 改变属性时,将执行下面语句
      if (key !== void 0) {
        // 根据 key 获取收集到的 dep 然后执行 add 方法
        add(depsMap.get(key));
      }
      // also run for iteration key on ADD | DELETE | Map.SET
      switch (type) {
        case "add" /* ADD */:
          if (!isArray(target)) {
            add(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
              add(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
          }
          else if (isIntegerKey(key)) {
            // new index added to array -> length changes
            add(depsMap.get('length'));
          }
          break;
        case "delete" /* DELETE */:
          if (!isArray(target)) {
            add(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
              add(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
          }
          break;
        case "set" /* SET */:
          if (isMap(target)) {
            add(depsMap.get(ITERATE_KEY));
          }
          break;
      }
    }
    const run = (effect) => {
      if ( effect.options.onTrigger) {
        effect.options.onTrigger({
          effect,
          target,
          key,
          type,
          newValue,
          oldValue,
          oldTarget
        });
      }
      if (effect.options.scheduler) {
      // 执行每个 effects 的scheduler 方法
        effect.options.scheduler(effect);
      }
      else {
        effect();
      }
    };
    // 遍历 effects 执行 run 方法
    effects.forEach(run);
  }

effect.options.scheduler 就是下面的 queueJob 方法

  function queueJob(job) {
    if ((!queue.length ||
      !queue.includes(job, isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex)) &&
      job !== currentPreFlushParentJob) {
      queue.push(job);
      queueFlush();
    }
  }

queueJob 方法就是将当前 job(effect) 添加到 queue 队列中,然后执行 queueFlush 方法开始一次微任务的添加,这样在事件循环处理事件队列时就可以执行 effect 方法,具体执行的代码为:

  function queueFlush() {
    if (!isFlushing && !isFlushPending) {
      isFlushPending = true;
      currentFlushPromise = resolvedPromise.then(flushJobs);
    }
  }
  function flushJobs(seen) {
    isFlushPending = false;
    isFlushing = true;
    {
      seen = seen || new Map();
    }
    flushPreFlushCbs(seen);
    // 对收集到的 effect 进行排序
    queue.sort((a, b) => getId(a) - getId(b));
    try {
      for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
        const job = queue[flushIndex];
        if (job) {
          if (true) {
            checkRecursiveUpdates(seen, job);
          }
          // 调用 callWithErrorHandling 执行每一个 effect
          callWithErrorHandling(job, null, 14 /* SCHEDULER */);
        }
      }
    }
    finally {
      flushIndex = 0;
      queue.length = 0;
      flushPostFlushCbs(seen);
      isFlushing = false;
      currentFlushPromise = null;
      // some postFlushCb queued jobs!
      // keep flushing until it drains.
      if (queue.length || pendingPostFlushCbs.length) {
        flushJobs(seen);
      }
    }
  }

 // 通过 callWithErrorHandling 执行每个 effect
  function callWithErrorHandling(fn, instance, type, args) {
    let res;
    try {
      // 执行每一个 effect 方法
      res = args ? fn(...args) : fn();
    }
    catch (err) {
      handleError(err, instance, type);
    }
    return res;
  }

更新依赖的过程总结如下:

  1. 属性变更时,触发 trigger 方法,注意这个时间的 type=set

  2. depsMap 取出当前属性收集的 effect 集合

  3. 遍历这些 effect ,执行每个 effect.options.scheduler(effect) 方法,将 efftct 添加到 queue

  4. 同时添加一个微任务到队列当中

  5. 执行微任务时,对 queue 中的 effect 进行排序,然后分别执行 effect 方法

# 关于深度对象的监听

Counter: {{ counter.cou }}
data() {
    return {
      counter: {
        cou: 1
      }
    }
  },

此时的处理步骤为:

  • 首先仍然会通过 resolveData(instance, dataOptions, publicThis) =>reactive(data) => createReactiveObject, 给当前 data 的值做 proxy 代理

  • 渲染组件访问 data 对象中的 counter 的属性,调用 track(target, "get" /* GET */, key),给当前属性 counter 添加事件 effect

  • 如果判断到 counter 属性的值是个对象,执行 isReadonly ? readonly(res) : reactive(res),相当于回到第一步,给当前 counter 值做 proxy 代理

  • 之后访问 counter 对象中访问 cou 的属性,又被 get 劫持,调用 track(target, "get" /* GET */, key),给当前属性 cou 添加事件 effect

可以看到对于深层的对象,仍然是需要对子对象做代理的,跟 vue2.0 一样,只是设置的代理的时机不同了

  • vue2.0 是初始化组件的时候遍历 data 及子属性对象,添加 Object.defineProperry 代理

  • vue3.0 只有 data 属性是在一开始的时候做 proxy 代理,之后是触发 get 后,再对子对象添加 proxy 代理

加个 proxy 粟子理解一下:

var handler = {
    get: function(obj, prop) {
        console.log(obj)
        return obj[prop]
    }
};

var p = new Proxy({a: {b: 34}}, handler)
//输入 p.a
//触发 console.log => - {a: {b: 34}}
//输出 {a: {b: 34}}

//输入 p.a.b
//触发 console.log => - {a: {b: 34}}
//输出 34

上面例子说明 prosy 只能代理第一层的属性

# 如果是添加属性呢?

假设我们给上面粟子中的 counter 对象在后期添加一个属性,Vue 又是怎么处理的呢?

this.counter.abc = 3

执行上面的语句时代码运行过程:

首先得访问 this.counter 属性,此时也会被 get 方法劫持,并执行 track(target, "get" /* GET */, key); 方法,但因为此时全局环境没有 activeEffect 事件,所以没有执行任何事件收集的动作

function track(target, type, key) {
  if (!shouldTrack || activeEffect === undefined) {
    return;
  }
}

然后 this.counter.abc = 3 时被 set 方法劫持

function set(target, key, value, receiver) {
  // 取旧值为 undefined
  const oldValue = target[key]; 
  if (!shallow) {
    value = toRaw(value); // 当前例子为 3 
    if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
      oldValue.value = value;
      return true;
    }
  }
  // 因为之前这个属性,hadKey 为false
  const hadKey = isArray(target) && isIntegerKey(key)
    ? Number(key) < target.length
    : hasOwn(target, key);
  // 给 target 添加这个属性
  const result = Reflect.set(target, key, value, receiver);
  if (target === toRaw(receiver)) {
    // hadKey 为 false,所以执行 trigger 方法
    if (!hadKey) {
      trigger(target, "add" /* ADD */, key, value);
    }
    else if (hasChanged(value, oldValue)) {
      trigger(target, "set" /* SET */, key, value, oldValue);
    }
  }
  return result;
};
 // 注意此时的 type = add
  function trigger(target, type, key, newValue, oldValue, oldTarget) {
    // 获取 target 收集器集合
    const depsMap = targetMap.get(target);
    if (!depsMap) {
      // never been tracked
      return;
    }
    const effects = new Set();
    const add = (effectsToAdd) => {
      if (effectsToAdd) {
        effectsToAdd.forEach(effect => {
          if (effect !== activeEffect || effect.allowRecurse) {
            effects.add(effect);
          }
        });
      }
    };
    if (type === "clear" /* CLEAR */) {
      // collection being cleared
      // trigger all effects for target
      depsMap.forEach(add);
    }
    else if (key === 'length' && isArray(target)) {
      depsMap.forEach((dep, key) => {
        if (key === 'length' || key >= newValue) {
          add(dep);
        }
      });
    }
    else {
      // key 有效
      if (key !== void 0) {
        // 但是因为当前 key 是新添加的,所以没有当前属性对应的 dep,我们继续往下走
        add(depsMap.get(key));
      }
      switch (type) {
        case "add" /* ADD */:
        // 当前 type 为 add 所以执行以下语句
          if (!isArray(target)) {
            // 取出 ITERATE_KEY 收集的 effect,之后的步骤添加到微任务队列触发 effect 事件,进行更新 
            add(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
              add(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
          }
          else if (isIntegerKey(key)) {
            // new index added to array -> length changes
            add(depsMap.get('length'));
          }
          break;
        case "delete" /* DELETE */:
          if (!isArray(target)) {
            add(depsMap.get(ITERATE_KEY));
            if (isMap(target)) {
              add(depsMap.get(MAP_KEY_ITERATE_KEY));
            }
          }
          break;
        case "set" /* SET */:
          if (isMap(target)) {
            add(depsMap.get(ITERATE_KEY));
          }
          break;
      }
    }
    const run = (effect) => {
      if ( effect.options.onTrigger) {
        effect.options.onTrigger({
          effect,
          target,
          key,
          type,
          newValue,
          oldValue,
          oldTarget
        });
      }
      if (effect.options.scheduler) {
        effect.options.scheduler(effect);
      }
      else {
        effect();
      }
    };
    effects.forEach(run);
  }

总结一下当给对象添加一个属性时的大致过程:

  1. 首先给当前 target 添加新的属性并赋值

  2. 调用trigger(target, "add" /* ADD */, key, value);,从 ITERATE_KEY 中取出 effect 事件加入到队列

ITERATE_KEY 是通过 proxy 劫持 ownKeys 属性添加的事件收集

  function ownKeys(target) {
    debugger
    track(target, "iterate" /* ITERATE */, isArray(target) ? 'length' : ITERATE_KEY);
    return Reflect.ownKeys(target);
  }

如果是对象 ITERATE_KEY 属性, 数组的话就添加 length 属性,至少是怎么触发的 ownKeys 方法 没找到位置...

  1. 当组件更新的时候就会重新取新增的属性,并给属性添加 effect 事件收集

# 数组类型的更新

将上面的例子改成数据类型: counter: [1,2,3],当我们使用 counter.push(4) 时看下代码的运行

return function get(target, key, receiver) {
  // 略
  const targetIsArray = isArray(target);
  if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
    return Reflect.get(arrayInstrumentations, key, receiver);
  }
  const res = Reflect.get(target, key, receiver);
  // 略
  return res;
};

// arrayInstrumentations 方法定义
  const arrayInstrumentations = {};
  ['includes', 'indexOf', 'lastIndexOf'].forEach(key => {
    const method = Array.prototype[key];
    arrayInstrumentations[key] = function (...args) {
      const arr = toRaw(this);
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, "get" /* GET */, i + '');
      }
      // we run the method using the original args first (which may be reactive)
      const res = method.apply(arr, args);
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        return method.apply(arr, args.map(toRaw));
      }
      else {
        return res;
      }
    };
  });
  ['push', 'pop', 'shift', 'unshift', 'splice'].forEach(key => {
    const method = Array.prototype[key];
    arrayInstrumentations[key] = function (...args) {
      pauseTracking();
      const res = method.apply(this, args);
      resetTracking();
      return res;
    };
  });

首先当访问 push 属性时,也会被 proxy 劫持,根据下文中的代码,使用将会执行 return Reflect.get(arrayInstrumentations, key, receiver);

arrayInstrumentations 是重写后的一些数组方法的集合,所以将返回 arrayInstrumentations.push 方法

之后执行 push(4) 时, 将执行以下语句

  // 实际做的事情是  trackStack.push(false);  给 trackStack 添加一个 false 标识
  pauseTracking();
  // 执行真正的 Array.push 方法,此时应该等价于 counter[counter.length = 3] = 4 ,所以在获取属性 `3` 时, 会被 set 劫持
  const res = method.apply(this, args);
  resetTracking();
  return res;

上面代码主要做的事情

  1. pauseTracking():实际做的事情是 trackStack.push(false); 给 trackStack 添加一个 false 标识, 暂停依赖收集,也就意味在此期间执行 track 时,不会去收集依赖

      function track(target, type, key) {
        if (!shouldTrack || activeEffect === undefined) {
          return;
        }
      }
    
  2. 当执行 const res = method.apply(this, args), 会被 set 劫持

  function createSetter(shallow = false) {
    // 当前例子 key = 3
    // value = 4
    return function set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      trigger(target, "set" /* SET */, key, value, oldValue);
      return result;
    };
  }

  function trigger(target, type, key, newValue, oldValue, oldTarget) {
    const depsMap = targetMap.get(target);
    const effects = new Set();
    const add = (effectsToAdd) => {
      if (effectsToAdd) {
        effectsToAdd.forEach(effect => {
          if (effect !== activeEffect || effect.allowRecurse) {
            effects.add(effect);
          }
        });
      }
    };
    if (type === "clear" /* CLEAR */) {
      depsMap.forEach(add);
    }
    else if (key === 'length' && isArray(target)) {
    }
    else {
      // also run for iteration key on ADD | DELETE | Map.SET
      switch (type) {
        case "add" /* ADD */:
          if (!isArray(target)) { }
          else if (isIntegerKey(key)) {
            // new index added to array -> length changes
            add(depsMap.get('length'));
          }
          break;
      }
    }
    effects.forEach(run);
  }

跟之前 set 流程类型类型,之后在 trigger 方法根据 length 取出当前对象收集的 effect,添加到微任务队列中进行视图更新

那么使用数组下标更改数组也是同理

# 总结

其实思路还是跟 vue2.0 的差不多

  1. Vue 在初始化的时候使用 proxy 方法代理,关于收集器两个版本的区别

    Vue2.0 会为每个属性实例化一个事件收集器 Dep

    Vue3.0 则通过全局变量 const targetMap = new WeakMap(); 来做事件收集容器,targetMapkey 是代理对象,valuedepsMap(一个收集事件容器的容器)

    depsMapnew Map() 结构,代理对象的每个属性收集的事件将存储在这个 depsMap 中,所以找属性收集的事件时,先根据这个属性所属对象从 targetMapdepsMap, 在根据具体属性从 depsMap 找事件

  2. 在组件渲染的时候,会生成一个 effect 方法,并将当前 effect 保存到全局 activeEffect中,之后在执行渲染操作的时候会读取 data 的属性, 并被 get 劫持,get 方法中将保存在全局的 activeEffect 收集到 depsMap

  3. 之后当 data 属性被更改时,会被 set 劫持,从 depsMap 取出 effect 并执行