当前位置: 首页 > news >正文

Vuejs设计与实现 —— 实现响应式系统

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入交流群!

Vuejs 三大核心模块:

  • Compiler 模块:涉及 AST 抽象语法树的内容,再通过 generateAST 生成渲染函数等
  • Runtime 模块:也称为 Renderer 模块,将虚拟 DOM 生成真实 DOM 元素,并渲染到浏览器上
  • Reactivity 模块:响应式系统,将 JavaScript 对象代理为数据模型,而当你修改数据模型时,视图会进行更新

其中响应式系统是 Vuejs 中的重要组成部分,相信这一部分大家都是深有体会的,下面就尝试实现响应式系统,目的是为了更好的了解响应式系统的设计与实现的过程。

响应式数据 & 副作用函数

什么是副作用函数?

顾名思义,副作用函数指的就是会产生 副作用函数

其中的 函数 不难理解,那么 副作用 是什么呢?

下面举个栗子,存在如下两个函数(具体作用看注释):

// 设置 body 中文本内容
function setTextForBody(text = 'hello vue3'){document.body.innerText = text
}// 获取 body 中的文本并输出
function getTextFromBody(){console.log("document.body = ", document.body.innerText)
}

当函数 setTextForBody 函数执行时,会将页面中 body 的内容默认设置为 'hello vue3',而 getTextFromBody 函数是负责获取 body 的文本内容。那么当 setTextForBody 调用时传入的参数不同,会导致 getTextFromBody 函数中获取到的内容也会发生改变,甚至是在其他函数中也有类似的设置或读取 body 中文本内容的操作,都会 受到直接或间接的影响,那么就可以称这个函数(这里是 setTextForBody 函数)产生了 副作用(effect)

什么是响应式数据?

请看下面的栗子:

// 初始数据
const data = { text: 'hello world' }// 副作用函数
function effect(){document.body.innerText = data.text
}// 修改数据
setTimeout(() => {data.text = 'hello vue3'
}, 3000);

如上代码中,副作用函数 effect 会设置 body 的文本内容为数据 data 对象中的 text 属性,而 setTimeout 则负责 3s 后将 data.text 的值进行修改。

期望的是,当代码执行 data.text = 'xxx' 的代码时,副作用函数 effect 可以自动执行,而省略手动调用的过程,那如果能实现这个目标,那么就可以将 data 对象称为响应式数据。

响应式数据的基本实现

通过上面的代码,不难发现(毕竟代码量很少):

  • 当副作用函数 effect 执行时,会通过 data.text 进行 读取操作
  • 当需要修改 text 字段值时,会通过 data.text = xxx 进行 设置操作
    其中 读取操作设置操作 正好对应 JavaScript 中的 gettersetter,在 Vue.js 2 中采用的是 Object.defineProperty 函数进行实现,而在 Vue.js 3 中已经转向 Proxy 的实现方式。

基本思路

  • 将原始数据进行代理,实现 gettersetter 函数
  • 当执行副作用 effect 函数时,会触发对应数据的 getter 函数,此时将这个 effect 函数保存到容器 bucket 中,等待在未来某时刻执行
  • 当执行 data.text = xxx 操作时,会触发对应数据的 setter 函数,此时从容器 bucket 中取出所有 effect 函数并执行它们
// 存储副作用函数的容器
const bucket = new Set()// 原始数据
const rawData = { text: 'hello world' }// 副作用函数
function effect(){document.body.innerText = proxyData.text
}// 响应式函数
function reactive(target){return new Proxy(target, {get(target, key){// 保存副作用函数 effectbucket.add(effect)// 返回访问的值return target[key]},set(target, key, newValue){// 设置新值target[key] = newValue// 从容器 bucket 中取出 effect 函数并执行bucket.forEach(fn => fn())// 表示设置成功return true}})
}// 对原始数据进行代理
const proxyData = reactive(rawData)

现在可以使用下面的代码来进行测试:

// 初始化执行,触发 getter 函数,收集 effect
effect()// 2s 后对 proxyData.text 进行修改
setTimeout(() => {console.log('定时器执行,触发修改')proxyData.text = 'hello vue3'
}, 2000)

得到的结果如下:

在这里插入图片描述

完善响应式系统

明确副目标对象和作用函数关系

存在缺陷

上面通过硬编码的形式进行的实现,存在着如下的缺陷:

  • 强制副作用的函数名为 effect,这会导致一旦产生副作用的函数名不是 effect,那么上述的代码实现就无法达到预期的效果
    • 最佳实现 应该是哪怕副作用函数是匿名的,也能被正确的进行收集到容器中,从而在未来某个班时刻被执行
  • 仅仅使用 set 数据结构作为副作用函数的容器,会导致 副作用函数和被操作目标的字段之间无法建立明确的关系
    • 目前的实现是将所有的副作用函数全部放到同一个容器中进行存储,导致的结果就是一旦某个被操作的目标字段进行更新操作,这会导致容器中的所有的副作用(即使是无关的)全部都会执行一遍
    • 最佳实现 应该是只执行和当前被操作目标中具体字段相关的副作用函数,可以是一个或多个

完善思路

  • 由于期望的副作用函数可以是任意形式的函数,因此需要一个全局变量 activeEffect 存储当前被注册的副作用函数
  • 为了 副作用函数和被操作目标的字段之间建立明确的关系,需要使用 WeakMap、Map、Set 重构对应的数据结构
    • 通过 WeakMap 创建依赖容器 bucket,它的键是对应不同的被操作目标对象,它的值的类型是一个 Map 对象,这个 Map 对象的键是对应被操作目标对象中的不同字段,其中每个字段对应的值是 Set 数据结构,里面可以存储多个对应的副作用函数

    • 可以通过下图进行辅助理解

      在这里插入图片描述

【关于数据结构选择上的解释】:使用 WeakMap 用来存储不同的被操作目标对象,是因为 WeakMap 中的键是弱引用的,简单的说一旦外部环境没有对这个目标对象的引用,那么垃圾回收机制可以正常进行回收;而 Map 的键属于强引用,即便外部没有对目标对象的引用,但这个 Map 本身的键也会被认为是对目标对象的引用,因此会导致垃圾回收无法正常进行。

具体代码实现

// 存储副作用函数
const bucket = new WeakMap()// 用于存储被注册的副作用函数
let activeEffect = null// 用于接收并注册副作用函数
function effect(fn) {// 保存 fnactiveEffect = fn// 执行 fn 函数,目的是初始化执行和触发 get 拦截fn()
}// 响应式数据
function reactive(target) {return new Proxy(target, {get(target, key) {console.log("get =", key, Reflect.get(target, key));// 没有注册副作用函数,直接返回数据if (!activeEffect) return Reflect.get(target, key)track(target, key)return Reflect.get(target, key)},set(target, key, newVal) {console.log("set ", key, newVal);target[key] = newValtrigger(target, key)return Reflect.set(target, key, newVal)}})
}// 收集依赖
function track(target, key) {// 从 bucket 获取 depsMap 的依赖关系let depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 从 depsMap 获取 deps 集合let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)
}// 触发依赖
function trigger(target, key) {// 获取对应的 depsMapconst depsMap = bucket.get(target)if (!depsMap) return// 获取对应的 depsconst deps = depsMap.get(key)// 执行相应的 effectdeps && deps.forEach(effect => effect())
}

动态清除无用副作用函数

存在缺陷

若执行下面的测试代码,那么会产生 遗留的副作用函数依赖

// 获得响应式数据
const data = reactive({text: 'hello world...',ok: true
})// 注册副作用函数
effect(() => {console.log('effect running ...')document.body.innerText = data.ok ? data.text : 'not ok'
})
  • 当初始化 ok = true 时执行,会产生依赖关系为:
    在这里插入图片描述

  • 当发生修改操作 ok = false 后,此时会执行对应副作用函数,同时意味着 data.text 字段将不会再被访问到,理想情况是此时 data.text 字段所对应的副作用函数依赖应该要被清除

完善思路

  • 每次副作用函数执行时,将副作用函数从所有与之有关联的依赖集合中进行删除
  • 当副作用函数执行完毕后,又会产生新的依赖关系,但这个新的依赖关系就不会包含遗留的副作用函数

具体代码实现

在这里插入图片描述

// 存储副作用函数
const bucket = new WeakMap()// 用于存储被注册的副作用函数
let activeEffect = null// 用于接收并注册副作用函数
function effect(fn) {const effectFn = () => {// 先调用 cleanup 函数完成旧依赖的清除工作cleanup(effectFn)// 保存 fnactiveEffect = effectFn// 执行 fn 函数,目的是初始化执行和触发 get 拦截fn()}// 用于存储所有与其关联的副作用函数的依赖集合effectFn.deps = []// 执行副作用函数effectFn()
}// 清除本次依赖相关的旧副作用函数
function cleanup(effectFn) {for (let i = 0; i < effectFn.deps.length; i++) {const deps = effectFn.deps[i]deps.delete(effectFn)}// 重置 effectFn.deps 数组effectFn.deps.length = 0
}// 响应式数据
function reactive(target) {return new Proxy(target, {get(target, key) {// 没有注册副作用函数,直接返回数据if (!activeEffect) return Reflect.get(target, key)track(target, key)return Reflect.get(target, key)},set(target, key, newVal) {target[key] = newValtrigger(target, key)return Reflect.set(target, key, newVal)}})
}// 收集依赖
function track(target, key) {// 从 bucket 获取 depsMap 的依赖关系let depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 从 depsMap 获取 deps 集合let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将与当前副作用函数存在联系的依赖集合 deps 添加到 activeEffect.deps 数组中activeEffect.deps.push(deps)
}// 触发依赖
function trigger(target, key) {// 获取对应的 depsMapconst depsMap = bucket.get(target)if (!depsMap) return// 获取对应的 depsconst effects = depsMap.get(key)// 构建新的 Set 避免递归const effectsToRun = new Set(effects)// 执行相应的 effecteffectsToRun.forEach(effectFn => effectFn())
}

支持嵌套的 effect 函数

为什么要支持嵌套 effect 函数?

Vuejs 来举例,如组件的嵌套就需要支持嵌套的 effect 函数,伪代码如下:

在这里插入图片描述

存在缺陷

假设存在如下的嵌套关系,存在的缺陷如下:

  • 当定时器执行并只更改 data.text 的值,此时只有 effect2 执行了,而期望的 effect1 却没执行执行
  • 原因是 目前使用全局变量 activeEffect 来存储通过 effect 函数注册的副作用函数,意味着同一时刻 activeEffect 存储的副作用函数只能有一个。当副作用函数发生嵌套时,内层的副作用函数的执行会覆盖 activeEffect 的值,当外部响应式数据进行依赖收集时,它们收集到的副作用函数将会是内层的副作用函数

image.png

// 获得响应式数据
const data = reactive({text: 'hello world...',ok: true
})// 注册副作用函数
effect(() => {effect(() => {console.log('effect2 执行:', data.ok)})console.log('effect1 执行:', data.text)
})console.log("bucket = ", bucket);setTimeout(() => {console.log('setTimeout 执行,修改 data.text 的值')data.text = 'hello vue3...'
}, 1000);

完善思路

通过副作用函数栈 effectStack 将正在执行的副作用函数入栈,等到副作用函数执行完毕后再弹出栈,并保证 activeEffect 始终是指向栈顶的副作用函数。

在这里插入图片描述

具体代码实现

// 存储副作用函数
const bucket = new WeakMap()// 用于存储被注册的副作用函数
let activeEffect = null// effect 栈
const effectStack = []// 用于接收并注册副作用函数
function effect(fn) {const effectFn = () => {// 先调用 cleanup 函数完成旧依赖的清除工作cleanup(effectFn)// 保存 fnactiveEffect = effectFn// 在副作用函数调用前,将副作用函数入栈effectStack.push(effectFn)// 执行 fn 函数,目的是初始化执行和触发 get 拦截fn()// 副作用函数执行完成后出栈effectStack.pop()// 将 activeEffect 指向栈顶(原先)的副作用函数activeEffect = effectStack[effectStack.length - 1]}// 用于存储所有与其关联的副作用函数的依赖集合effectFn.deps = []// 执行副作用函数effectFn()
}// 清除本次依赖相关的旧副作用函数
function cleanup(effectFn) {for (let i = 0; i < effectFn.deps.length; i++) {const deps = effectFn.deps[i]deps.delete(effectFn)}// 重置 effectFn.deps 数组effectFn.deps.length = 0
}// 响应式数据
function reactive(target) {return new Proxy(target, {get(target, key) {// 没有注册副作用函数,直接返回数据if (!activeEffect) return Reflect.get(target, key)track(target, key)return Reflect.get(target, key)},set(target, key, newVal) {target[key] = newValtrigger(target, key)return Reflect.set(target, key, newVal)}})
}// 收集依赖
function track(target, key) {// 从 bucket 获取 depsMap 的依赖关系let depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 从 depsMap 获取 deps 集合let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将与当前副作用函数存在联系的依赖集合 deps 添加到 activeEffect.deps 数组中activeEffect.deps.push(deps)
}// 触发依赖
function trigger(target, key) {// 获取对应的 depsMapconst depsMap = bucket.get(target)if (!depsMap) return// 获取对应的 depsconst effects = depsMap.get(key)// 构建新的 Set 避免递归const effectsToRun = new Set(effects)// 执行相应的 effecteffectsToRun.forEach(effectFn => effectFn())
}

避免无限递归循环

存在缺陷

如下面的例子,就会产生无限循环:

// 获得响应式数据
const data = reactive({count: 1
})// 注册副作用函数
effect(() => {data.count++ // 等价于 data.count = data.count + 1
})

其中,既会读取 data.count 的值,又会设置 data.count 的值,每次 trigger 操作触发时,本次还没有执行完,又触发了下一次的 trigger 操作,这就会产生无限递归调用自身,导致栈溢出。

完善思路

trigger 操作发生时添加是否执行副作用函数的条件:若 trigger 触发执行的副作用函数与当前的正则执行的副作用函数相同,则不触发执行

具体代码实现

// 存储副作用函数
const bucket = new WeakMap()// 用于存储被注册的副作用函数
let activeEffect = null// effect 栈
const effectStack = []// 用于接收并注册副作用函数
function effect(fn) {const effectFn = () => {// 先调用 cleanup 函数完成旧依赖的清除工作cleanup(effectFn)// 保存 fnactiveEffect = effectFn// 在副作用函数调用前,将副作用函数入栈effectStack.push(effectFn)// 执行 fn 函数,目的是初始化执行和触发 get 拦截fn()// 副作用函数执行完成后出栈effectStack.pop()// 将 activeEffect 指向栈顶(原先)的副作用函数activeEffect = effectStack[effectStack.length - 1]}// 用于存储所有与其关联的副作用函数的依赖集合effectFn.deps = []// 执行副作用函数effectFn()
}// 清除本次依赖相关的旧副作用函数
function cleanup(effectFn) {for (let i = 0; i < effectFn.deps.length; i++) {const deps = effectFn.deps[i]deps.delete(effectFn)}// 重置 effectFn.deps 数组effectFn.deps.length = 0
}// 响应式数据
function reactive(target) {return new Proxy(target, {get(target, key) {// 没有注册副作用函数,直接返回数据if (!activeEffect) return Reflect.get(target, key)track(target, key)return Reflect.get(target, key)},set(target, key, newVal) {target[key] = newValtrigger(target, key)return Reflect.set(target, key, newVal)}})
}// 收集依赖
function track(target, key) {// 从 bucket 获取 depsMap 的依赖关系let depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 从 depsMap 获取 deps 集合let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将与当前副作用函数存在联系的依赖集合 deps 添加到 activeEffect.deps 数组中activeEffect.deps.push(deps)
}// 触发依赖
function trigger(target, key) {// 获取对应的 depsMapconst depsMap = bucket.get(target)if (!depsMap) return// 获取对应的 depsconst effects = depsMap.get(key)// 构建新的 Set 避免递归const effectsToRun = new Set(effects)// 执行相应的 effecteffectsToRun && effectsToRun.forEach(effectFn => {// 避免递归调用自身if (effectFn !== activeEffect) effectFn()})
}

实现可调度执行 — 调度器函数

什么是可调度性?

可调度指的是当 trigger 触发副作用函数重新执行时,提供给使用者决定副作用函数执行的时机、次数和方式。

通过下面的代码举个栗子:

// 获得响应式数据
const data = reactive({count: 1
})// 注册副作用函数
effect(() => {console.log(data.count)
})data.count++console.log('结束了')

其对应的数据输出结果为:1 2 ‘结束了’,假设使用者需要的输出顺序是:1 ‘结束了’ 2,那么就需要当前的响应式系统支持 调度

实现思路

  • 给现有的 effect 函数多添加一个可选参数 options,允许使用者指定调度器,例如:
    effect(()=>{console.log(data.count)
    },
    {// 将调度器设置名为 scheduler 的函数scheduler(fn){...}
    })
    
  • effect 函数中注册副作用函数时,将这个 options 选项挂载到对应的副作用函数上
  • trigger 函数触发副作用函数重新执行时,通过直接调用 options 中传入的调度器函数,把控制权移交给使用者

具体代码实现

// 存储副作用函数
const bucket = new WeakMap()// 用于存储被注册的副作用函数
let activeEffect = null// effect 栈
const effectStack = []// 用于接收并注册副作用函数
function effect(fn, options = {}) {const effectFn = () => {// 先调用 cleanup 函数完成旧依赖的清除工作cleanup(effectFn)// 保存 fnactiveEffect = effectFn// 在副作用函数调用前,将副作用函数入栈effectStack.push(effectFn)// 执行 fn 函数,目的是初始化执行和触发 get 拦截fn()// 副作用函数执行完成后出栈effectStack.pop()// 将 activeEffect 指向栈顶(原先)的副作用函数activeEffect = effectStack[effectStack.length - 1]}// 将 options 挂载到 effectFn 上effectFn.options = options// 用于存储所有与其关联的副作用函数的依赖集合effectFn.deps = []// 执行副作用函数effectFn()
}// 清除本次依赖相关的旧副作用函数
function cleanup(effectFn) {for (let i = 0; i < effectFn.deps.length; i++) {const deps = effectFn.deps[i]deps.delete(effectFn)}// 重置 effectFn.deps 数组effectFn.deps.length = 0
}// 响应式数据
function reactive(target) {return new Proxy(target, {get(target, key) {// 没有注册副作用函数,直接返回数据if (!activeEffect) return Reflect.get(target, key)track(target, key)return Reflect.get(target, key)},set(target, key, newVal) {target[key] = newValtrigger(target, key)return Reflect.set(target, key, newVal)}})
}// 收集依赖
function track(target, key) {// 从 bucket 获取 depsMap 的依赖关系let depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 从 depsMap 获取 deps 集合let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将与当前副作用函数存在联系的依赖集合 deps 添加到 activeEffect.deps 数组中activeEffect.deps.push(deps)
}// 触发依赖
function trigger(target, key) {// 获取对应的 depsMapconst depsMap = bucket.get(target)if (!depsMap) return// 获取对应的 depsconst effects = depsMap.get(key)// 构建新的 Set 避免递归const effectsToRun = new Set()effects && effects.forEach(effectFn => {// 避免递归调用自身if (effectFn !== activeEffect) effectsToRun.add(effectFn)})// 是否执行调度器函数effectsToRun.forEach(effectFn => {// 若副作用函数存在调度器,则调用调度器,并将 effectFn 函数作为参数传递if (effectFn.options.scheduler) {effectFn.options.scheduler(effectFn)} else {// 否则直接执行副作用函数effectFn()}})
}

基于调度器控制执行次数

为什么需要控制执行次数?

直接通过如下栗子进行解释:

// 获得响应式数据
const data = reactive({count: 1
})// 注册副作用函数
effect(() => {console.log(data.count);
})data.count++
data.count++

在没有指定调度器时,以上代码执行后输出结果为:1 2 3,但假设其中的 2 只是个过渡阶段,使用者只关心最后的结果 3,那么执行三次打印操作就是多余的,即期望输出为:1 3

实现思路

  • 定义一个任务队列 jobQueue,选择 Set 数据结构,目的是利用它的自动去重功能
  • 每次调度执行时,先将当前副作用函数添加到 jobQueue 队列中
  • 定义一个 flushJob 函数刷新 jobQueue 队列中的副作用函数
    • 其中需要设定一个 isFlushing 表示正在刷新的标志,用于去判断是否需要执行,只有当 isFlushing = false 时才需要执行,保证 flushJob 函数在一个周期内只调用一次
    • 最后通过 promise.then 来将刷新 jobQueue 队列的执行添加到微任务队列中

即支持通过如下方式进行调用:

// 获得响应式数据
const data = reactive({count: 1
})// 注册副作用函数
effect(() => {console.log(data.count);
}, {scheduler(effectFn) {// 将副作用函数添加到 jobQueue 队列中jobQueue.add(effectFn)// 调用 flushJob 刷新队列,减少不必要的执行flushJob()}
})data.count++
data.count++

具体代码实现

// 存储副作用函数
const bucket = new WeakMap()// 用于存储被注册的副作用函数
let activeEffect = null// effect 栈
const effectStack = []// 定义 jobQueue 任务队列
const jobQueue = new Set()
// 通过 promise 微任务实现异步执行
const resolvedPromise = Promise.resolve()
function nextTick(fn) {return fn ? resolvedPromise.then(fn) : resolvedPromise
}
// 表示当前是否正在刷新队列
let isFlushing = false// 刷新队列函数
function flushJob() {// 当前正在刷新队列,则直接结束if (isFlushing) return// 一旦需要执行刷新队列,先将 isFlushing 置为 falseisFlushing = true// 在微任务队列中刷新 jobQueue 队列nextTick(() => {jobQueue.forEach(job => job())}).finally(() => {// 刷新队列结束后,重置 isFlushingisFlushing = false})
}// 用于接收并注册副作用函数
function effect(fn, options = {}) {const effectFn = () => {// 先调用 cleanup 函数完成旧依赖的清除工作cleanup(effectFn)// 保存 fnactiveEffect = effectFn// 在副作用函数调用前,将副作用函数入栈effectStack.push(effectFn)// 执行 fn 函数,目的是初始化执行和触发 get 拦截fn()// 副作用函数执行完成后出栈effectStack.pop()// 将 activeEffect 指向栈顶(原先)的副作用函数activeEffect = effectStack[effectStack.length - 1]}// 将 options 挂载到 effectFn 上effectFn.options = options// 用于存储所有与其关联的副作用函数的依赖集合effectFn.deps = []// 执行副作用函数effectFn()
}// 清除本次依赖相关的旧副作用函数
function cleanup(effectFn) {for (let i = 0; i < effectFn.deps.length; i++) {const deps = effectFn.deps[i]deps.delete(effectFn)}// 重置 effectFn.deps 数组effectFn.deps.length = 0
}// 响应式数据
function reactive(target) {return new Proxy(target, {get(target, key) {// 没有注册副作用函数,直接返回数据if (!activeEffect) return Reflect.get(target, key)track(target, key)return Reflect.get(target, key)},set(target, key, newVal) {target[key] = newValtrigger(target, key)return Reflect.set(target, key, newVal)}})
}// 收集依赖
function track(target, key) {// 从 bucket 获取 depsMap 的依赖关系let depsMap = bucket.get(target)if (!depsMap) {bucket.set(target, (depsMap = new Map()))}// 从 depsMap 获取 deps 集合let deps = depsMap.get(key)if (!deps) {depsMap.set(key, (deps = new Set()))}deps.add(activeEffect)// 将与当前副作用函数存在联系的依赖集合 deps 添加到 activeEffect.deps 数组中activeEffect.deps.push(deps)
}// 触发依赖
function trigger(target, key) {// 获取对应的 depsMapconst depsMap = bucket.get(target)if (!depsMap) return// 获取对应的 depsconst effects = depsMap.get(key)// 构建新的 Set 避免递归const effectsToRun = new Set()effects && effects.forEach(effectFn => {// 避免递归调用自身if (effectFn !== activeEffect) effectsToRun.add(effectFn)})// 是否执行调度器函数effectsToRun.forEach(effectFn => {// 若副作用函数存在调度器,则调用调度器,并将 effectFn 函数作为参数传递if (effectFn.options.scheduler) {effectFn.options.scheduler(effectFn)} else {// 否则直接执行副作用函数effectFn()}})
}

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新,也可快速加入交流群!

以上内容都是 Vue.js 内部响应式系统的实现思路,但其内部拥有一个更完善处理机制和边界情况,作为学习者而言了解其设计思路和设计原因其实也足够了,不过作为 coder 不要总是省略动手的过程。


http://www.mrgr.cn/news/55888.html

相关文章:

  • C语言 | Leetcode C语言题解之第480题滑动窗口中位数
  • idea中文国际化转码
  • 保研推荐信模板
  • 算法笔记day07
  • Next-Token Prediction is All You Need 智源发布原生多模态大模型Emu3
  • 电影评论网站开发:Spring Boot技术指南
  • 【MATLAB源码-第186期】matlab基于MLE算法的8天线阵列DOA估计仿真,对比粗估计、精确估计输出RMSE对比图。
  • DeepSpeed:所有人都能用的超大规模模型训练工具
  • 开源神器!CodeFormer:一键去除马赛克,高清修复照片视频
  • 将VSCode界面的显示语言改为简体中文,切换VScode界面的显示语言
  • 智能码二维码ZHINENGMA.CN在设备巡检中的使用效果如何?
  • 前端性能优化之卡顿篇
  • 数据的全量加载和增量加载
  • pdman在关系图中展示表的后排字段
  • WORFBENCH:一个创新的评估基准,目的是全面测试大型语言模型在生成复杂工作流 方面的性能。
  • Centos7.6版本安装mysql详细步骤
  • Vue3 学习笔记(一)Vue3 介绍及环境部署
  • Docker 教程十一(Docker Machine)
  • 如何借助通达信API构建自动化交易系统?
  • # 更正 Excel 表格中 #VALUE! 错误
  • 信号源(函数发生器)的输出幅度是什么
  • 目前我国网络安全人才市场状况
  • MyBatis入门之一对一关联关系(示例)
  • 北斗小型化NTP时间服务器|北斗智能终端设备
  • 如何处理让人眼花缭乱的销售数据?使用报表制作工具轻松解决
  • 基于FreeRTOS的LWIP移植