vue3.5.18源码:reactive和单例、代理和发布订阅者之间的关系

reactive是一个特别常用的API,在Vue3中,reactive函数是用来创建一个响应式对象。底层用到了哪些技术呢?我们一起来看一下。 学完本文,你将获得以下知识:

  • reactive函数底层实现原理
  • reactive函数与单例模式之间的关系
  • reactive函数与代理模式之间的关系
  • reactive函数中类的继承关系
  • reactive函数与发布订阅者模式之间的关系
ts 复制代码
<body>
    <div id="app"></div>
    <script>
        const App = {
            template:`
                {{ reactiveObj.count }}
                <button @click="changeCount">修改数据</button>
            `,
            setup() {
                debugger;
                // 定义响应式对象
                const reactiveObj = Vue.reactive({
                    count: 1
                })
                // 定义修改数据的方法
                const changeCount = function() {
                    reactiveObj.count += 1;
                }
                // 暴露给模板
                return {
                    reactiveObj,
                    changeCount
                };
            }
        };
        // 创建应用并挂载
        const app = Vue.createApp(App);
        app.mount("#app");
    </script>
</body>

以上例子中,我们定义了一个响应式对象reactiveobject,然后通过changeCount方法修改了reactiveObj中的count属性,此时,页面中的数据也会随之更新。下面我们来看一下reactive函数是如何实现的。

ts 复制代码
// reactive函数
function reactive(target) {
  if (isReadonly(target)) {
    return target;
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  );
}
// createReactiveObject函数
function createReactiveObject(
  target,
  isReadonly2,
  baseHandlers,
  collectionHandlers,
  proxyMap
) {
  // 如果不是一个对象的话,直接返回。
  const targetType = getTargetType(target);
  if (targetType === 0) {
    return target;
  }
  // 如果在proxyMap中能找到缓存的话,就直接返回。
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  // 代理模式:在访问target之前先访问collectionHandlers
  const proxy = new Proxy(
    target,
    targetType === 2 ? collectionHandlers : baseHandlers
  );
  proxyMap.set(target, proxy);
  return proxy;
}

一、单例模式

单例模式指的是全局对象中有且只有一个实例对象,当前例子:

ts 复制代码
const existingProxy = proxyMap.get(target);
if (existingProxy) {
  return existingProxy;
}

以上如果existingProxy存在,直接返回,否则才会执行后续逻辑,确保了target有且只有一个代理对象。

接下来看代理模式。

二、代理模式

以上例子中代码为:

ts 复制代码
// 这里targetType为1,执行的是baseHandlers
const proxy = new Proxy(
  target,
  targetType === 2 ? collectionHandlers : baseHandlers
);

我们知道代理模式指的是在访问目标对象之前先访问代理对象,那么在reactive函数中,target就是reactiveObj,那么在访问reactiveObj之前,会先访问baseHandlers函数,继续看baseHandlers函数中用到的继承关系。

三、类的继承

baseHandlers来源于开头例子中的mutableHandlers

ts 复制代码
// 处理函数实例
const mutableHandlers = new MutableReactiveHandler();
//  MutableReactiveHandler类
class MutableReactiveHandler extends BaseReactiveHandler {
  constructor(isShallow2 = false) {
    super(false, isShallow2);
  }
  set(target, key, value, receiver) {
    let oldValue = target[key];
    // ...
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key);
    const result = Reflect.set(
      target,
      key,
      value,
      isRef(target) ? target : receiver
    );
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, "add", key, value);
      } else if (hasChanged(value, oldValue)) {
        // 在修改数据时,触发set函数,触发试图更新
        trigger(target, "set", key, value, oldValue);
      }
    }
    return result;
  }
  deleteProperty(target, key) {}
  has(target, key) {}
  ownKeys(target) {}
}
// BaseReactiveHandler类
class BaseReactiveHandler {
  constructor(_isReadonly = false, _isShallow = false) {
    this._isReadonly = _isReadonly;
    this._isShallow = _isShallow;
  }
  get(target, key, receiver) {
    // 省略其他支线逻辑
    const isReadonly2 = this._isReadonly,
      isShallow2 = this._isShallow;
    if (!isReadonly2) {
      track(target, "get", key);
    }
    return res;
  }
}

从以上例子中,可以看出,mutableHandlersMutableReactiveHandler的实例,而MutableReactiveHandler继承了BaseReactiveHandler,在BaseReactiveHandler中,定义了get方法,该方法中调用了track方法,该方法用于收集依赖。在MutableReactiveHandler中,定义了set方法,该方法中调用了trigger方法,该方法用于触发更新。

接下来,我们看一下在track中发布者是如何收集订阅者的,在trigger中发布者又是如何触发订阅者更新视图的。

四、发布订阅者模式

1、Dep

再构建vnode时,会访问到reactiveObj中的count属性。先触发get函数,接着会触发track函数:

ts 复制代码
// 全局对象targetMap
const targetMap = /* @__PURE__ */ new WeakMap();
// track函数
function track(target, type, key) {
  if (shouldTrack && activeSub) {
    // 获取targetMap
    let depsMap = targetMap.get(target);
    // 如果depsMap不存在,以target为key,以创建一个Map对象为value,创建一个target和depsMap的映射关系
    if (!depsMap) {
      targetMap.set(target, (depsMap = /* @__PURE__ */ new Map()));
    }
    // 在depsMap中获取key对应的dep。
    let dep = depsMap.get(key);
    // 如果dep不存在,以当前key值为key,创建它与Dep实例之间的映射关系
    if (!dep) {
      depsMap.set(key, (dep = new Dep()));
      dep.map = depsMap;
      dep.key = key;
    }
    {
      dep.track({
        target,
        type,
        key,
      });
    }
  }
}

targetMap中可以通过target(即ReactiveObj)获取到depsMap,在depsMap中可以通过key(即count)获取到dep,接下来看 dep对应的Dep类的实现。

ts 复制代码
// Dep类,作为发布者和订阅者之间的关系管理类
class Dep {
  constructor(computed) {
    this.computed = computed;
    this.version = 0;
    // 核心: dep和当前活跃的sub之间的关系
    this.activeLink = void 0;
    this.subs = void 0;
    this.map = void 0;
    this.key = void 0;
    // 订阅者个数
    this.sc = 0;
    this.__v_skip = true;
    {
      this.subsHead = void 0;
    }
  }
  track(debugInfo) {
    // 依赖收集,省略逻辑...
  }
  trigger(debugInfo) {
    // 派发更新,省略逻辑...
  }
  notify(debugInfo) {
    // 通知更新,省略逻辑...
  }
}

至此,从reactiveObj开始,就找到了最终控制发布订阅者关系的Dep类。

这里我们先看看控制视图的订阅者对应的ReactiveEffect类。

2、ReactiveEffect

ReactiveEffect的实例化对象就是一个订阅者。渲染过程的调度过程就是由其实例化对象中的run方法进行的,核心代码如下:

ts 复制代码
// ReactiveEffect类
class ReactiveEffect {
  constructor(fn) {
    this.fn = fn;
    this.deps = void 0;
    this.depsTail = void 0;
    this.flags = 1 | 4;
    this.next = void 0;

    this.cleanup = void 0;
    this.scheduler = void 0;
    if (activeEffectScope && activeEffectScope.active) {
      activeEffectScope.effects.push(this);
    }
  }
  pause() {
    // 省略逻辑...
  }
  resume() {
    // 省略逻辑...
  }
  notify() {
    // 省略逻辑...
  }
  run() {
    // 当前活跃的订阅者就是this,即effect
    activeSub = this;
    try {
      return this.fn();
    } finally {
      // 省略逻辑...
    }
  }
  stop() {
    // 省略逻辑...
  }
  trigger() {
    // 省略逻辑...
  }
  runIfDirty() {
    // 省略逻辑...
  }
  get dirty() {
    // 省略逻辑...
  }
}

在上述对象中,this.fn=fn,实例化时指向的是实例化的执行函数componentUpdateFn,获取vnodepatch的逻辑都在其中。const update = instance.update = effect.run.bind(effect),执行update时,实际执行的是ReactiveEffectrun方法。此时,就将当前实例赋值给了activeSub

说明白了DepReactiveEffect 的作用,再来看看订阅者是如何和发布者发生关联的。

3、构建关系(时机:首次渲染)

在首次渲染生成vnode时,会访问到reactiveObj中的count,进而执行到BaseReactiveHandler类中的get函数。该函数调用track(target, "get", key),即dep.track({ target, type, key });进行发布者和订阅者的关系构建,核心代码如下:

ts 复制代码
// this.dep.track
track(debugInfo) {
  if (!activeSub || !shouldTrack || activeSub === this.computed) {
    return;
  }
  let link = this.activeLink;
  if (link === void 0 || link.sub !== activeSub) {
    // 建立activeSub和dep的关系
    link = this.activeLink = new Link(activeSub, this);
    if (!activeSub.deps) {
      // 订阅者的依赖deps指向link
      activeSub.deps = activeSub.depsTail = link;
    } else {
      // 链表的形式可以管理多个订阅者
      link.prevDep = activeSub.depsTail;
      activeSub.depsTail.nextDep = link;
      activeSub.depsTail = link;
    }
    addSub(link);
  }
  return link;
}
// Link类的实例将包含sub和dep两个属性,分别指向依赖和订阅者
class Link {
  constructor(sub, dep) {
    this.sub = sub;
    this.dep = dep;
    this.version = dep.version;
    // 链表的形式管理dep和sub
    this.nextDep = this.prevDep = this.nextSub = this.prevSub = this.prevActiveLink = void 0;
  }
}
// addSub函数
function addSub(link) {
  link.dep.sc++;
  if (link.sub.flags & 4) {
    if (link.dep.subsHead === void 0) {
      // link.dep.subsHead作为链表头,起初也指向link
      link.dep.subsHead = link;
    }
    // 依赖的订阅者subs也指向link
    link.dep.subs = link;
  }
}

这里需要重点关注的是activeSub.deps = activeSub.depsTail = linklink.dep.subs = link,两者都指向了同一个link对象,该对象包含depsub属性,这样activeSubdep之间建立了关系。实现了你中有我, 我中有你的双向依赖关系。从而数据变化时的找到对应的订阅者,进入触发更新的流程。

4、触发更新(时机:数据修改)

当执行reactiveObj.count += 1; 操作时,会触发trigger(target, "set", key, value, oldValue);函数,进而执行到this.dep.trigger函数,派发更新的核心代码如下:

依赖MutableReactiveHandler中的set逻辑,进而执行其中的trigger逻辑:

ts 复制代码
// MutableReactiveHandler的trigger函数
trigger(target, "set", key, value, oldValue);
// 以下函数中省略所有支线逻辑,如需要请查阅源码
function trigger(target, type, key, newValue, oldValue, oldTarget) {
  const depsMap = targetMap.get(target);
  const run = (dep) => {
    if (dep) {
      {
        // 执行dep中的trigger函数
        dep.trigger({
          target,
          type,
          key,
          newValue,
          oldValue,
          oldTarget,
        });
      }
    }
  };
  if (key !== void 0 || depsMap.has(void 0)) {
    // 开始执行run函数
    run(depsMap.get(key));
  }
}

其中通过depsMap = targetMap.get(target)获得了desMap,即实例化后的dep,我们接着看其中的trigger函数:

ts 复制代码
// Dep类中的trigger函数
trigger(debugInfo) {
  this.version++;
  globalVersion++;
  this.notify(debugInfo);
}
// Dep的notify函数
notify() {
  for (let link = this.subs; link; link = link.prevSub) {
    if (link.sub.notify()) {
      ;
      link.sub.dep.notify();
    }
  }
}

当执行到ink.sub.notify()时,即执行到了订阅者类ReactiveEffectnotify函数,核心代码如下:

ts 复制代码
// sub的notify函数
notify() {
  if (this.flags & 2 && !(this.flags & 32)) {
    return;
  }
  if (!(this.flags & 8)) {
    batch(this);
  }
}
// batch函数
function batch(sub, isComputed = false) {
  sub.flags |= 8;
  if (isComputed) {
    sub.next = batchedComputed;
    batchedComputed = sub;
    return;
  }
  sub.next = batchedSub;
  // batchedSub指向第一个sub
  batchedSub = sub;
}

当以上逻辑结束时,继续回到DependBatch方法:

ts 复制代码
// dep的endBatch方法
function endBatch() {
  if (--batchDepth > 0) {
    return;
  }
  // 省略计算属性相关的逻辑...
  while (batchedSub) {
    let e = batchedSub;
    batchedSub = void 0;
    while (e) {
      const next = e.next;
      e.next = void 0;
      e.flags &= -9;
      if (e.flags & 1) {
        try {
          // 执行e(batchedSub)的trigger()方法
          e.trigger();
        } catch (err) {
          if (!error) error = err;
        }
      }
      e = next;
    }
  }
  if (error) throw error;
}
// sub的trigger()方法
trigger() {
  if (this.flags & 64) {
    pausedQueueEffects.add(this);
  } else if (this.scheduler) {
    // 即effect.scheduler = () => queueJob(job);
    this.scheduler();
  } else {
    this.runIfDirty();
  }
}

再看 queueJob 的核心逻辑

ts 复制代码
function queueJob(job) {
  if (!(job.flags & 1)) {
    const jobId = getId(job);
    const lastJob = queue[queue.length - 1];
    if (!lastJob || (!(job.flags & 2) && jobId >= getId(lastJob))) {
      // 将当前任务插入到数组尾部
      queue.push(job);
    } else {
      // 根据jobId,将其移动到合适的位置
      queue.splice(findInsertionIndex(jobId), 0, job);
    }
    job.flags |= 1;
    queueFlush();
  }
}
// queueFlush
function queueFlush() {
  if (!currentFlushPromise) {
    // flushJobs是异步任务,得等下个异步队列才执行
    currentFlushPromise = resolvedPromise.then(flushJobs);
  }
}

下一个异步队列,执行的任务:

ts 复制代码
function flushJobs(seen) {
  {
    seen = seen || /* @__PURE__ */ new Map();
  }
  const check = (job) => checkRecursiveUpdates(seen, job);
  try {
    for (flushIndex = 0; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex];
      if (job && !(job.flags & 8)) {
        if (check(job)) {
          continue;
        }
        if (job.flags & 4) {
          job.flags &= ~1;
        }
        // 在错误处理函数中执行job
        callWithErrorHandling(job, job.i, job.i ? 15 : 14);
        if (!(job.flags & 4)) {
          job.flags &= ~1;
        }
      }
    }
  } finally {
    for (; flushIndex < queue.length; flushIndex++) {
      const job = queue[flushIndex];
      if (job) {
        job.flags &= -2;
      }
    }
    flushIndex = -1;
    queue.length = 0;
    flushPostFlushCbs(seen);
    currentFlushPromise = null;
    if (queue.length || pendingPostFlushCbs.length) {
      flushJobs(seen);
    }
  }
}
// 错误处理函数
function callWithErrorHandling(fn, instance, type, args) {
    try {
      return args ? fn(...args) : fn();
    } catch (err) {
      handleError(err, instance, type);
    }
  }
// 因为job = instance.job = effect.runIfDirty.bind(effect);,所以,fn就是runIfDirty函数
runIfDirty() {
  if (isDirty(this)) {
    // 这里就是最终的渲染逻辑
    this.run();
  }
}

以上,就是从数据reactiveObj.count变化,到订阅者执行渲染逻辑的全过程。

总结:reactive函数会返回一个代理对象proxy,在生成vnode的过程中需要访问到reactiveObj.count,首先会访问到过起代理对象中的执行逻辑baseHandlersbaseHandlers中的基类和继承类分别包含setget函数,访问时数据是触发get中的track函数,进行依赖收集;在数据改变时,会让订阅者执行link.sub.notify()的操作,从而执行数据变化后的最终渲染函数。

相关推荐
青红光硫化黑9 分钟前
React-native之组件
javascript·react native·react.js
菠萝+冰11 分钟前
在 React 中,父子组件之间的通信(传参和传方法)
前端·javascript·react.js
庚云13 分钟前
一套代码如何同时适配移动端和pc端
前端
Jinuss15 分钟前
Vue3源码reactivity响应式篇Reflect和Proxy详解
前端·vue3
海天胜景23 分钟前
vue3 el-select 默认选中第一个
前端·javascript·vue.js
小小怪下士_---_42 分钟前
uniapp开发微信小程序自定义导航栏
前端·vue.js·微信小程序·小程序·uni-app
前端W44 分钟前
腾讯地图组件使用说明文档
前端
页面魔术1 小时前
无虚拟dom怎么又流行起来了?
前端·javascript·vue.js
胡gh1 小时前
如何聊懒加载,只说个懒可不行
前端·react.js·面试