UNPKG

@purevue/reactivity

Version:

## 📖 Introduction

1,011 lines (987 loc) 33.8 kB
'use strict'; var shared = require('@purevue/shared'); const isObject = (val) => val !== null && typeof val === 'object'; const isFunction = (val) => typeof val === 'function'; const hasChanged = (value, oldValue) => !Object.is(value, oldValue); function isReactive(value) { if (isReadonly(value)) { return isReactive(value['__v_raw']); } return !!(value && value['__v_isReactive']); } function isReadonly(value) { return !!(value && value['__v_isReadonly']); } function isShallow(value) { return !!(value && value['__v_isShallow']); } function isProxy(value) { return value ? !!value['__v_raw'] : false; } function isRef(val) { return !!(val && val['__v_isRef'] === true); } function toRaw(observed) { const raw = observed && observed['__v_raw']; return raw ? toRaw(raw) : observed; } /** * Vue 内部用于标记响应式对象的特殊属性 key。 */ var ReactiveFlags; (function (ReactiveFlags) { /** * 跳过响应式转换标志。 * * - 当对象上存在 `__v_skip: true` 时, * `reactive()` 不会再对该对象进行代理包装。 * - 典型场景:一些已经被处理过的对象,避免重复代理。 */ ReactiveFlags["SKIP"] = "__v_skip"; /** * 是否为响应式对象。 * * - 由 `reactive()` 包装过的对象上会自动带有该属性。 * - 常用于 Vue 内部做类型识别。 */ ReactiveFlags["IS_REACTIVE"] = "__v_isReactive"; /** * 是否为只读对象。 * * - 由 `readonly()` 包装过的对象上会自动带有该属性。 * - 修改只读对象会触发警告。 */ ReactiveFlags["IS_READONLY"] = "__v_isReadonly"; /** * 是否为浅层响应式对象。 * * - 由 `shallowReactive()` 或 `shallowReadonly()` 创建。 * - 只代理第一层属性,嵌套对象不会被递归转换。 */ ReactiveFlags["IS_SHALLOW"] = "__v_isShallow"; /** * 获取原始对象。 * * - 通过 `toRaw(obj)` 实现:返回代理背后的原始对象。 * - 内部依赖 `__v_raw` 属性来识别。 */ ReactiveFlags["RAW"] = "__v_raw"; /** * 是否为 Ref 对象。 * * - 由 `ref()` 或 `shallowRef()` 创建。 * - Ref 对象内部有 `.value` 属性,访问时会触发依赖收集。 */ ReactiveFlags["IS_REF"] = "__v_isRef"; })(ReactiveFlags || (ReactiveFlags = {})); /** * ReactiveEffect 内部使用的位标志(Bit Flags),用于表示 effect 的状态、行为和生命周期。 * * 每个标志占用一位,可以通过按位运算组合使用。 */ var EffectFlags; (function (EffectFlags) { /** * effect 当前处于激活状态,可以收集依赖并被触发。 * * 当不处于 ACTIVE 状态时,effect 被视为已停止,不会追踪依赖。 */ EffectFlags[EffectFlags["ACTIVE"] = 1] = "ACTIVE"; /** * effect 当前正在运行中。 * * 用于避免 effect 在执行过程中递归触发自身。 */ EffectFlags[EffectFlags["RUNNING"] = 2] = "RUNNING"; /** * effect 当前正在追踪依赖。 * * 在响应式属性的 getter 执行期间设置,表示可以收集依赖。 */ EffectFlags[EffectFlags["TRACKING"] = 4] = "TRACKING"; /** * effect 已经被通知,将在批量更新中执行。 * * 用于防止同一个 effect 被多次加入 flush 队列。 */ EffectFlags[EffectFlags["NOTIFIED"] = 8] = "NOTIFIED"; /** * effect 是脏的,需要重新计算。 * * 主要用于 computed 属性,用于标记缓存的值已过期。 */ EffectFlags[EffectFlags["DIRTY"] = 16] = "DIRTY"; /** * 允许 effect 自己递归触发自身。 * * 通常用于某些场景下需要自触发的 watcher 回调。 */ EffectFlags[EffectFlags["ALLOW_RECURSE"] = 32] = "ALLOW_RECURSE"; /** * effect 被暂停,不会被触发。 * * 可以临时挂起 effect,而不完全停止它。 */ EffectFlags[EffectFlags["PAUSED"] = 64] = "PAUSED"; /** * 标记 computed effect 至少已经被计算过一次。 * * 用于区分尚未运行的 computed 与已缓存的结果。 */ EffectFlags[EffectFlags["EVALUATED"] = 128] = "EVALUATED"; })(EffectFlags || (EffectFlags = {})); /** * 当前批量更新的嵌套深度 * 用于支持嵌套的 trigger 调用,如果 batchDepth > 0,则不会立即 flush */ let batchDepth = 0; /** * 同步批量执行队列 * 存储所有被 NOTIFIED 标记的 ReactiveEffect,用于同步批量触发 */ // TODO: Vue 3.5 后使用链表结构替代数组 let batchedSubs = []; /** * 将 effect 加入批量队列 * @param sub 要批量执行的 ReactiveEffect */ function batch(sub) { // 标记该 effect 已经被通知,避免重复加入队列 sub.flags |= EffectFlags.NOTIFIED; // TODO: 暂不处理 computed effect // if (isComputed) { // sub.next = batchedComputed // batchedComputed = sub // return // } // 将 effect 放入批量队列 batchedSubs.push(sub); } /** * 开始批量更新 * 每次 trigger 或 notify 时调用,增加 batchDepth * 支持嵌套 trigger 时延迟 flush */ function startBatch() { batchDepth++; } /** * 结束批量更新 * 当所有嵌套 trigger 完成后,flush 批量队列 * @remarks * 1. batchDepth > 1 表示还有外层 trigger 正在执行,不立即 flush * 2. 当 batchDepth 减为 0 时,按后进先出的顺序触发队列中的 effect * 3. 每个 effect 执行前会清除 NOTIFIED 标记 */ function endBatch() { // 如果还有外层 batch 正在进行,直接返回,延迟 flush if (--batchDepth > 0) { return; } // TODO: 暂不处理 computed effect // 按后进先出触发队列中的 effect let batchedSub = batchedSubs.pop(); while (batchedSub) { // 清除 NOTIFIED 标记,以便下次再次触发 batchedSub.flags &= ~EffectFlags.NOTIFIED; // 只有 ACTIVE 状态的 effect 才能被触发 if (batchedSub.flags & EffectFlags.ACTIVE) { try { batchedSub.trigger(); } catch (err) { throw err; } } batchedSub = batchedSubs.pop(); } } // ReactiveEffect 是 Vue 3 响应式系统中的 核心类 // 它表示一个“副作用函数”(effect),即在数据变化时需要重新执行的逻辑。 // 可以把它看作是 Vue 2 中 Watcher 的替代品,但更轻量、更灵活、且支持嵌套、调度器等高级功能。 /** 当前活跃的副作用 */ // TODO: vue 3.5+ 依赖收集重构,activeEffect 更改为 activeSub(Subscriber) exports.activeEffect = null; /** * 是否应该收集依赖 * 某些阶段需暂停收集依赖,如:执行 setup 函数 */ exports.shouldTrack = true; const trackStack = []; function pauseTracking() { trackStack.push(exports.shouldTrack); exports.shouldTrack = false; } function enableTracking() { trackStack.push(exports.shouldTrack); exports.shouldTrack = true; } function resetTracking() { const last = trackStack.pop(); exports.shouldTrack = last === undefined ? true : last; } /** * Vue 响应式系统中的“观察者”(Observer) * - 通过 fn 表示副作用逻辑 * - 依赖响应式数据,当数据变化时被重新调度执行 */ class ReactiveEffect { fn; /** 记录当前这个 effect 被哪些响应式属性的 dep(依赖集合)引用了,用于在 stop() 或更新时清理依赖关系 */ // 双向追踪,反向记录 /* Vue 3 的响应式系统中: • dep (Set<effect>):被观察对象记录所有观察者 • effect.deps (Dep[]):观察者反过来记录自己在哪些对象中被追踪(用于清理) 二者配合,构成了可追踪 + 可解绑的响应系统,比 Vue 2 更高效可靠。 */ deps = []; /** 位标志,用于表示 effect 的状态、行为和生命周期 */ flags = EffectFlags.ACTIVE | EffectFlags.TRACKING; /** effect 的“更新调度器”。如果有,它接管 effect 的执行逻辑;如果没有,effect 默认立即运行。 */ scheduler; constructor(fn) { this.fn = fn; } /** * 执行副作用函数并收集依赖 * - 如果已经 stop,则只执行 fn,不进行依赖收集 * - 如果仍处于 active 状态,则: * 1. 清理旧依赖 * 2. 切换 activeEffect * 3. 执行 fn(触发响应式读取,从而建立新的依赖关系) * 4. 恢复 activeEffect */ run() { if (!(this.flags & EffectFlags.ACTIVE)) { // 如果 effect 已经被 stop 掉,直接执行一次 fn,不参与响应式追踪 return this.fn(); } // 依赖切换前先清理旧的依赖 /* 例子(不清理的后果) const effect = new ReactiveEffect(() => { console.log(flag.value ? count.value : 'no count') }) */ cleanupEffect(this); // 依赖收集 const prevEffect = exports.activeEffect; const prevShouldTrack = exports.shouldTrack; exports.activeEffect = this; exports.shouldTrack = true; try { // 执行副作用函数,并触发函数中响应性数据的依赖收集 return this.fn(); } finally { // 清除依赖 exports.activeEffect = prevEffect; exports.shouldTrack = prevShouldTrack; this.flags &= ~EffectFlags.RUNNING; } } /** * 停止 effect,使其不再收集依赖并参与更新 */ stop() { if (this.flags & EffectFlags.ACTIVE) { cleanupEffect(this); this.flags &= ~EffectFlags.ACTIVE; } } /** * 触发副作用函数的重新执行 */ trigger() { if (this.scheduler) { this.scheduler(); } else { this.run(); } } /** * 响应式依赖通知时调用(批处理队列) */ notify() { if (this.flags & EffectFlags.RUNNING && !(this.flags & EffectFlags.ALLOW_RECURSE)) { return; } if (!(this.flags & EffectFlags.NOTIFIED)) { batch(this); } } } /** * 清理 effect 的所有依赖 */ function cleanupEffect(effect) { const { deps } = effect; for (let i = 0; i < deps.length; i++) { deps[i].subs.delete(effect); } effect.deps.length = 0; } /** * 注册副作用函数,并立即执行一次,触发依赖收集 */ function effect(fn) { const eff = new ReactiveEffect(fn); eff.run(); return eff; } // Array 方法改写 // 1.只读方法的修复: include 等方法的依赖收集 // 2.修改方法的优化: push、pop 等方法暂停依赖收集,保护依赖收集不被污染 const arrayInstrumentations = { __proto__: null, push(...args) { return noTracking(this, 'push', args); }, // TODO: 其他方法的改写 }; // 数组的原生方法(push、pop、shift、unshift 等)内部会读写 length 或访问数组索引 // 改写数组方法 主要目的是防止内部访问 length 或索引时收集依赖,保护依赖收集不被污染,不是为了触发响应式更新 // 这点和 Vue2 不一样(Proxy 可以拦截数组索引和 length 的操作,而 Object.defineProperty 不行) // 触发更新(trigger)是 在 set / add / delete 里完成的,即数组真实修改索引或 length 时才触发 function noTracking(self, method, args = []) { // 暂停依赖收集,防止方法内部访问 length 或索引时触发错误的依赖 pauseTracking(); startBatch(); const res = toRaw(self)[method].apply(self, args); endBatch(); resetTracking(); return res; } /** * Dep 依赖管理器 * 每个响应式属性都会对应一个 Dep,管理它的订阅者 (effect) */ class Dep { /** 当前 dep 的版本号(触发 trigger 时递增) */ version = 0; /** 关联的 computed(如果有的话) */ // TODO: 处理 ComputedRefImpl // computed?: ComputedRefImpl /** 保存订阅该 dep 的 effects */ // TODO: 源码 Vue 3.4+ 换成了双向链表结构:能更高效地维护 effect 访问顺序,避免重复遍历 subs = new Set(); constructor() { } /** * 收集依赖 */ track(debugInfo) { // 如果没有激活的 effect,或者禁止追踪,则不收集 if (!exports.activeEffect || !exports.shouldTrack) { return; } // 将当前 effect 添加到 dep.subs if (!this.subs.has(exports.activeEffect)) { debugInfo && console.log('[Dep track]', debugInfo); this.subs.add(exports.activeEffect); // 同时反向记录 dep 到 effect 的依赖表 if (!exports.activeEffect.deps.includes(this)) { exports.activeEffect.deps.push(this); } } } /** * 触发依赖更新 */ trigger(debugInfo) { this.version++; this.notify(); } notify(debugInfo) { startBatch(); try { const effectsToRun = new Set(); // 收集所有订阅者(避免在运行时修改 set 导致死循环) this.subs.forEach((effect) => { if (effect !== exports.activeEffect) { effectsToRun.add(effect); } }); // 逐个执行订阅者 effectsToRun.forEach((effect) => { debugInfo && console.log('[Dep trigger]', debugInfo); effect.notify(); }); } finally { endBatch(); } } } /** * 一个特殊的唯一 Symbol,表示对象的「遍历依赖」。 * * - 当在 effect 里写 `for...in` 或者 `Object.keys(obj)`, * Vue 就会对这个 ITERATE_KEY 建立依赖。 * - 这样一旦对象新增 / 删除属性(ADD / DELETE),就会触发这些依赖。 */ const ITERATE_KEY = Symbol('Object iterate'); /** * 一个特殊的唯一 Symbol,表示数组的「遍历依赖」。 * * - 当在 effect 里写 `for...of` 或者 `arr.forEach`, * Vue 就会对这个 ARRAY_ITERATE_KEY 建立依赖。 * - 这样一旦数组新增 / 删除索引,就会触发这些依赖。 */ const ARRAY_ITERATE_KEY = Symbol('Array iterate'); // 全局依赖图:targetMap 记录所有响应式对象 reactive 中,每个属性对应的 effect 集合 const targetMap = new WeakMap(); /** * track 收集依赖 * 每当读取响应式对象的某个属性时,触发依赖收集,把当前正在运行的 effect 与这个属性建立联系 * @param target 响应式对象 * @param key 被访问的属性键 */ function track(target, key) { // 如果当前没有正在运行的副作用函数,或者不应该收集依赖,直接跳过 if (!exports.activeEffect || !exports.shouldTrack) return; // 获取 target 对应的所有属性依赖映射 let depsMap = targetMap.get(target); if (!depsMap) { // 如果不存在则新建一个 Map 来存储该 target 的属性依赖 depsMap = new Map(); targetMap.set(target, depsMap); } // 获取指定 key 对应的依赖集合(一个 Dep,其实是 Set<ReactiveEffect>) let dep = depsMap.get(key); if (!dep) { // 如果不存在则新建一个 Dep 用于收集依赖 dep = new Dep(); depsMap.set(key, dep); } // 将当前 activeEffect 添加到 dep 中,实现依赖收集 dep.track(); } /** * 触发类型枚举,表示对响应式对象属性的操作类型 */ var TriggerOpTypes; (function (TriggerOpTypes) { TriggerOpTypes["SET"] = "set"; TriggerOpTypes["ADD"] = "add"; TriggerOpTypes["DELETE"] = "delete"; TriggerOpTypes["CLEAR"] = "clear"; })(TriggerOpTypes || (TriggerOpTypes = {})); /** * trigger 派发通知 * 当修改响应式对象的某个属性时,找到所有依赖这个属性的 effect,并触发它们重新运行 * @param target 响应式对象 * @param type 操作类型,参考 TriggerOpTypes * @param key 被操作的属性键 */ function trigger(target, type, key) { // 获取 target 对应的所有属性依赖映射 const depsMap = targetMap.get(target); if (!depsMap) return; // 没有依赖记录,说明没人在监听这个属性,直接返回 // 如 Map.clear() if (type === TriggerOpTypes.CLEAR) { // 如果是清空操作,触发所有依赖 depsMap.forEach((dep) => { dep.trigger(); }); } else { // 判断 target 是否为数组 const targetIsArray = shared.isArray(target); // 判断 key 是否为数组索引(整数键) const isArrayIndex = targetIsArray && shared.isIntegerKey(key); if (targetIsArray && key === 'length') { // TODO // 如果修改的是数组的 length 属性 // 这里的逻辑暂时注释,实际应触发长度相关依赖 // const newLength = Number(newValue) // depsMap.forEach((dep, key) => { // if (key === 'length' || key === ARRAY_ITERATE_KEY || (!isSymbol(key) && key >= newLength)) { // run(dep) // } // }) return; } // 触发指定 key 的依赖 const dep = depsMap.get(key); if (dep) dep.trigger(); // 如果是数组索引,触发数组迭代器相关依赖 if (isArrayIndex) { depsMap.get(ARRAY_ITERATE_KEY)?.trigger(); } // 根据不同操作类型,触发额外的依赖 switch (type) { case TriggerOpTypes.ADD: if (!targetIsArray) { // 新增普通对象属性,触发对象的迭代器依赖 depsMap.get(ITERATE_KEY)?.trigger(); } else if (isArrayIndex) { // 新增数组索引,触发 length 属性的通知,如 Array.push() depsMap.get('length')?.notify(); } break; case TriggerOpTypes.DELETE: if (!targetIsArray) { // 删除普通对象属性,触发对象的迭代器依赖 depsMap.get(ITERATE_KEY)?.trigger(); } break; case TriggerOpTypes.SET: // 修改已有属性,之前已经触发过对应 key 的依赖,无需额外操作 break; } } } /**x * MutableReactiveHandler 是 Vue 3 响应式系统中用于创建响应式对象的 Proxy handler。 * 它拦截对目标对象的读取、设置、删除等操作,实现依赖收集和触发更新的功能。 * 该类支持深度响应式处理,并针对数组方法做了特殊处理以优化性能和行为一致性。 */ class MutableReactiveHandler { _isReadonly; _isShallow; constructor(_isReadonly = false, _isShallow = false) { this._isReadonly = _isReadonly; this._isShallow = _isShallow; } /** * 拦截读取操作,返回对应的属性值。 * 该方法会进行依赖收集,并递归地将嵌套对象转为响应式对象。 * 对数组的特定方法做了重写以支持依赖追踪和触发。 * @param target 目标对象 * @param key 被读取的属性键 * @param receiver Proxy 或继承对象 * @returns 属性对应的值,可能是响应式对象或原始值 */ get(target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) { return true; } else if (key === ReactiveFlags.RAW) { return target; } // Array 方法改写 // 1.只读方法的修复: include 等方法的依赖收集 // 2.修改方法的优化: push、pop 等方法的依赖触发(暂停依赖收集) if (shared.isArray(target) && arrayInstrumentations[key]) { return arrayInstrumentations[key]; } const res = Reflect.get(target, key, isRef(target) ? target : receiver); track(target, key); // 递归响应式处理 if (isObject(res)) { return reactive(res); } return res; } /** * 拦截设置操作,设置属性值并触发相关依赖更新。 * 支持处理 ref 类型的特殊赋值逻辑,以及数组的新增或修改操作。 * @param target 目标对象 * @param key 被设置的属性键 * @param value 新的属性值 * @param receiver Proxy 或继承对象 * @returns 设置操作是否成功 */ set(target, key, value, receiver) { let oldValue = target[key]; oldValue = toRaw(oldValue); value = toRaw(value); // 如果旧值是 ref,新值不是 ref → 自动更新 .value if (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value; return true; } // 处理新增的情况,如 obj.a = 'xx', arr.push('xx') const hadKey = shared.isArray(target) && shared.isIntegerKey(key) ? Number(key) < target.length : shared.hasOwn(target, key); // 通过 Reflect.set 进行真实赋值 const result = Reflect.set(target, key, value, isRef(target) ? target : receiver); // 只有 目标对象确实是 receiver 本身 时才触发依赖,防止其子类实例的改变触发父类 if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key); } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key); } } return result; } /** * 拦截删除操作,删除目标对象的指定属性,并触发依赖更新。 * @param target 目标对象 * @param key 要删除的属性键 * @returns 删除操作是否成功 */ deleteProperty(target, key) { const hadKey = shared.hasOwn(target, key); // const oldValue = target[key] const result = Reflect.deleteProperty(target, key); if (result && hadKey) { trigger(target, TriggerOpTypes.DELETE, key); } return result; } /** * 拦截 in 操作符,判断属性是否存在于目标对象中。 * 同时进行依赖收集,避免遗漏对属性存在性的响应式追踪。 * @param target 目标对象 * @param key 要检测的属性键 * @returns 属性是否存在于目标对象中 */ has(target, key) { const result = Reflect.has(target, key); if (!shared.isSymbol(key)) { track(target, key); } return result; } /** * 拦截 Object.getOwnPropertyKeys 等操作,返回目标对象自身的所有属性键。 * 同时进行依赖收集,用于追踪对象的遍历操作。 * @param target 目标对象 * @returns 目标对象的所有自身属性键组成的数组 */ ownKeys(target) { track(target, shared.isArray(target) ? 'length' : ITERATE_KEY); return Reflect.ownKeys(target); } } /** 用于缓存响应式对象,避免重复创建 */ const reactiveMap = new WeakMap(); // TODO: 暂不处理 shallow 和 readonly function reactive(target) { if (!isObject(target)) return target; // 已经被代理过的对象直接返回缓存 const existingProxy = reactiveMap.get(target); if (existingProxy) return existingProxy; // 创建一个新的代理对象 // TODO: 源码区分 普通对象/数组: baseHandlers , 集合类对象如Map/Set: collectionHandlers // Map/Set 的 .get()、.set()、.delete() 不是普通的属性访问,Vue 会重写方法 const handler = new MutableReactiveHandler(); const proxy = new Proxy(target, handler); // 缓存代理对象 reactiveMap.set(target, proxy); return proxy; } function toReactive(value) { return isObject(value) ? reactive(value) : value; } // 观察者模式中的Subject class RefImpl { /** 原始值 */ _rawValue; /** value值 */ _value; /** ref标识 */ __v_isRef = true; /** 中介容器,把 Subject 和多个 Observer 关联起来 */ dep = new Dep(); constructor(value) { // 保存原始值到_rawValue this._rawValue = toRaw(value); // 如果是对象(引用类型),使用reactive将对象转为响应式的(Proxy),因此将一个对象传入ref,实际上也是调用了reactive this._value = toReactive(value); } get value() { // 收集依赖 this.dep.track(); return this._value; } set value(newValue) { const oldValue = this._rawValue; newValue = toRaw(newValue); // 如果值改变,才会触发依赖 if (hasChanged(newValue, oldValue)) { // 更新值 this._rawValue = newValue; // 判断是否是对象,进行赋值 this._value = toReactive(newValue); // 派发通知 this.dep.trigger(); } } } /** * 实现 Vue3 的 proxyRefs(ref.value 自动脱壳) */ function proxyRefs(obj) { return new Proxy(obj, { get(target, key) { const val = target[key]; // 如果是 ref,就自动返回 .value return isRef(val) ? val.value : val; }, set(target, key, newVal) { const oldVal = target[key]; // 如果旧值是 ref,更新 .value,否则直接替换 if (isRef(oldVal)) { oldVal.value = newVal; return true; } else { return Reflect.set(target, key, newVal); } }, }); } function createRef(rawValue) { if (isRef(rawValue)) { return rawValue; } return new RefImpl(rawValue); } function ref(value) { return createRef(value); } function unref(ref) { return isRef(ref) ? ref.value : ref; } /** * 全局副作用调度队列 * 存储待执行的副作用函数 * 使用微任务进行统一调度,避免重复执行 */ const queue = []; /** 当前 flush 的 Promise 对象,用于防止重复调度 */ let currentFlushPromise = null; /** 当前正在执行队列的位置索引 */ let flushIndex = -1; /** * 调度队列执行 * 如果当前没有 flush 正在进行,则通过微任务调度 flushJobs */ function queueFlush() { if (!currentFlushPromise) { // 将 flushJobs 放入微任务队列,保证本轮事件循环结束后执行 currentFlushPromise = Promise.resolve().then(flushJobs); // 可选方案:宏任务(优先级低,会有延迟) // currentFlushPromise = setTimeout(flushJobs, 0) } } /** * 执行队列中的所有副作用任务 * @remarks * 1. 遍历 queue 执行每个 ReactiveEffect 的 run 方法 * 2. 执行完成后重置状态,清空队列 */ function flushJobs() { try { for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { const job = queue[flushIndex]; if (job) { job(); } } } finally { // 重置索引,标记当前 flush 完成 flushIndex = -1; currentFlushPromise = null; queue.length = 0; // 清空队列 } } /** * 将一个副作用任务加入调度队列 * @param job - 待调度的 ReactiveEffect * @remarks * 1. 使用 includes 去重,避免同一 effect 在同一轮循环中重复执行 * 2. 调用 queueFlush,将 flushJobs 放入微任务队列 */ function queueJob(job) { if (!queue.includes(job)) { queue.push(job); queueFlush(); } } // 用于缓存已访问的对象,避免循环引用导致的无限递归 const seenObjects = new Set(); /** 用于递归地访问对象的所有嵌套属性 */ function traverse(value) { const res = _traverse(value, seenObjects); seenObjects.clear(); // 遍历完成后清空缓存 return res; } function _traverse(value, seen) { // 不是对象或是 null,直接返回 if (!shared.isObject(value)) return value; // 如果已经访问过,直接返回,避免循环引用 if (seen.has(value)) return value; seen.add(value); if (Array.isArray(value)) { // 如果是数组,递归访问每一项 for (let i = 0; i < value.length; i++) { _traverse(value[i], seen); } } else { // 如果是对象,递归访问每个键的值 for (const key in value) { _traverse(value[key], seen); } } return value; } function doWatch(source, cb, options = {}) { let { deep = false, immediate = false } = options; let getter; // 解析 source if (isRef(source)) { // 如果是 ref,直接返回其值 getter = () => source.value; } else if (isReactive(source)) { // 如果是 reactive,对象进行深度递归 getter = () => source; // 强制深度监视 deep = true; } // 多个源:逐一解析 else if (Array.isArray(source)) { getter = () => source.map((s) => (isRef(s) ? s.value : isReactive(s) ? traverse(s) : s)); } else if (isFunction(source)) { // 如果是 getter 函数,直接使用 getter = source; } else { // NOPE getter = () => { }; } // 处理深度监视 if (deep) { const baseGetter = getter; getter = () => traverse(baseGetter()); } const INITIAL_WATCHER_VALUE = Symbol('INITIAL_WATCHER_VALUE'); let oldValue = INITIAL_WATCHER_VALUE; // 执行回调函数 const fn = () => { if (cb) { const newValue = getter(); if (deep || hasChanged(newValue, oldValue)) { if (immediate || oldValue !== INITIAL_WATCHER_VALUE) { cb(newValue, oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue); } oldValue = newValue; } } else { getter(); // 仅执行副作用,无回调 } }; // 创建 ReactiveEffect const effect = new ReactiveEffect(fn); // 加入异步调度,防止多次触发 const job = () => effect.run(); effect.scheduler = () => queueJob(job); // 激活依赖收集 effect.run(); return () => { effect.stop(); }; } function watch(source, cb, options) { return doWatch(source, cb, options); } function watchEffect(effect, options) { return doWatch(effect, null, options); } // TODO: 源码Vue 3.5+ 的 ComputedRefImpl 实际上本身是 Subscriber // 旧版 的 ComputedRefImpl 是一个特殊的 RefImpl class ComputedRefImpl { getter; setter; /** value 值 */ _value; /** 数据是否为脏数据(失效) */ _dirty = true; /** 本身作为依赖时,收集副作用 */ dep = new Dep(); /** 本身作为副作用时,被依赖收集 */ effect; /** ref标识 */ __v_isRef = true; constructor(getter, setter) { this.getter = getter; this.setter = setter; this.effect = new ReactiveEffect(() => { // 当依赖改变时,触发 if (!this._dirty) { // 标记为脏数据(需要重新计算) this._dirty = true; // TODO: 只标记,不触发,保持懒特性 this.dep.trigger(); } return getter(); }); // 启动该副作用 this._value = this.effect.run(); } get value() { if (this._dirty) { this._value = this.effect.run(); // 重新计算 this._dirty = false; // 重置脏标记 } this.dep.track(); return this._value; // 返回缓存的值 } set value(newValue) { if (this.setter) { this.setter(newValue); } else { console.warn('Write operation failed: computed value is readonly'); } } } function computed(getterOrOptions) { let getter; let setter; if (shared.isFunction(getterOrOptions)) { getter = getterOrOptions; } else { getter = getterOrOptions.get; setter = getterOrOptions.set; } return new ComputedRefImpl(getter, setter); } exports.ComputedRef = ComputedRefImpl; exports.ReactiveEffect = ReactiveEffect; exports.Ref = RefImpl; exports.batch = batch; exports.computed = computed; exports.effect = effect; exports.enableTracking = enableTracking; exports.endBatch = endBatch; exports.hasChanged = hasChanged; exports.isFunction = isFunction; exports.isObject = isObject; exports.isProxy = isProxy; exports.isReactive = isReactive; exports.isReadonly = isReadonly; exports.isRef = isRef; exports.isShallow = isShallow; exports.pauseTracking = pauseTracking; exports.proxyRefs = proxyRefs; exports.queueJob = queueJob; exports.reactive = reactive; exports.reactiveMap = reactiveMap; exports.ref = ref; exports.resetTracking = resetTracking; exports.startBatch = startBatch; exports.toRaw = toRaw; exports.toReactive = toReactive; exports.unref = unref; exports.watch = watch; exports.watchEffect = watchEffect; //# sourceMappingURL=index.cjs.js.map