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

手写 vue 源码 ===:自定义调度器、递归调用规避与深度代理

目录

引言

自定义调度器(Scheduler)

什么是调度器?

调度器的实现原理

自定义调度器的实际应用

切面编程(AOP)思想在调度器中的应用

递归调用规避

递归调用的问题

Vue 如何规避递归调用

深度代理(Deep Proxy)

什么是深度代理?

Vue 中的深度代理实现

bind 方法的应用

bind 方法的作用

为什么需要 bind?

总结


 

引言

在上一篇文章中,我们深入探讨了 Vue 响应式系统中的依赖清理机制。本文将继续深入 Vue 响应式系统的其他高级特性,包括自定义调度器、递归调用规避、深度代理等,并探讨其中涉及的bind方法、切面编程思想以及递归实现流程。

自定义调度器(Scheduler)

什么是调度器?

调度器是 Vue 响应式系统中的一个重要概念,它允许我们控制 effect 的执行时机和方式。当响应式数据发生变化时,默认情况下会立即触发相关的 effect 执行。而通过自定义调度器,我们可以改变这一默认行为。

调度器的实现原理

在 Vue 的响应式系统中,调度器是通过 effect函数的第二个参数传入的:

export function effect(fn, options: any = {}) {// 创建一个 effect 只要依赖的属性变化,就会重新执行const _effect = new ReactiveEffect(fn, () => {_effect.run();});// 执行_effect.run();if (options) {Object.assign(_effect, options); //用用户传入的配置,来覆盖默认的配置}const runner = _effect.run.bind(_effect); //bind 改变this指向 并且返回一个函数runner.effect = _effect; //将effect挂载到runner上return runner;
}

当我们创建一个ReactiveEffect 实例时,可以传入一个调度器函数作为第二个参数。当响应式数据变化触发 effect 执行时,会先检查是否有调度器,如果有则执行调度器而不是直接执行 effect:

export function triggerEffects(dep) {for (let effect of dep.keys()) {// 如果不是正在执行,那么就执行调度器if (!effect._runing) {if (effect.scheduler) {effect.scheduler();}}}
}

自定义调度器的实际应用

让我们看一个实际的例子,展示如何使用自定义调度器:

//调度器
let runner = effect(() => {document.body.innerHTML = `<h1>${state.flag ? state.name : state.age}</h1>`
}, {scheduler: () => {console.log('scheduler执行了,不自动更新');//AOP 面向切面编程runner() //重新渲染}
})

在这个例子中,我们创建了一个 effect 并传入了一个自定义调度器。当响应式数据变化时,不会直接执行 effect 的回调函数,而是先执行调度器函数。在调度器函数中,我们可以决定是否执行 effect,或者何时执行 effect。

切面编程(AOP)思想在调度器中的应用

上面的例子中提到了 AOP(面向切面编程)。AOP 是一种编程范式,它允许我们在不修改原有代码的情况下,通过"切面"的方式添加新的行为。

在 Vue 的响应式系统中,调度器就是一个典型的 AOP 应用:

  1. 原始行为:响应式数据变化时执行 effect
  2. 切面:调度器函数
  3. 新行为:响应式数据变化时先执行调度器,由调度器决定何时执行 effect

这种设计使得我们可以在不修改 Vue 核心代码的情况下,灵活地控制 effect 的执行方式,例如:

  • 延迟执行 effect(使用 setTimeout
  • 批量执行 effect(收集多个变化后一次性执行)
  • 条件执行 effect(根据某些条件决定是否执行)

递归调用规避

递归调用的问题

在响应式系统中,如果在 effect 中修改了触发该 effect 的响应式数据,就会导致递归调用,例如:

effect(() => {state.count = state.count + 1
})

这段代码会导致无限循环:读取 state.count 会收集依赖,修改 state.count 会触发依赖执行,而依赖执行又会修改 state.count,如此循环往复。

Vue 如何规避递归调用

Vue 通过在ReactiveEffect类中添加一个_runing 标志来规避递归调用:

class ReactiveEffect {_trackId = 0; // 当前的 effect 执行了几次deps = []; // 当前的 effect 依赖了哪些属性_depsLength = 0; // 当前的 effect 依赖的属性有多少个_runing = 0; // 当前的 effect 是否在执行public active = true; //默认是响应式的constructor(public fn, public scheduler) {}run() {// 如果当前状态是停止的,执行后,啥都不做if (!this.active) {return this.fn();}let lastEffect = activeEffect;try {activeEffect = this; // 当前的 effect 「依赖收集」// 每次执行前需要将上一次的依赖清空 effect.depspreCleanEffect(this);this._runing++; //执行前,将当前的effect设置为正在执行return this.fn(); //依赖收集 「state.name ,state.age」} finally {this._runing--; //执行后,将当前的effect设置为未执行postCleanEffect(this);activeEffect = lastEffect; // 执行完毕后 恢复上一次的 activeEffect}}
}

在 run 方法中,执行前将 _runing 设为 1,执行后将 _runing 设为 0。在 triggerEffects 函数中,会检查 effect 是否正在执行,如果是则不触发:

export function triggerEffects(dep) {for (let effect of dep.keys()) {// 如果不是正在执行,那么就执行调度器if (!effect._runing) {if (effect.scheduler) {effect.scheduler();}}}
}

深度代理(Deep Proxy)

什么是深度代理?

深度代理是指不仅对对象本身进行代理,还对对象的嵌套属性(如果是对象)也进行代理。这样,无论访问对象的哪一层属性,都能触发依赖收集和更新。

Vue 中的深度代理实现

Vue 的深度代理是在 getter 中实现的:

export const mutableHandlers: ProxyHandler<any> = {get(target: any, key: any, receiver: any) {// 如果访问的是代理对象的属性,直接返回if (key === ReactiveFlags.IS_REACTIVE) {return true;}// 依赖收集「收集这个对象上的这个属性,和 effect 关联」// 「当取值的时候,应该让 响应式属性,和 effect 建立联系」track(target, key);let res = Reflect.get(target, key, receiver); // 等价于receiver[key]if (isObject(res)) {// 如果取值是对象,则递归代理return reactive(res);}return res;},
}

深度代理的递归流程如下:

  • 调用 reactive(obj) 创建一个代理对象
  • 当访问代理对象的属性时,触发 get 方法
  • 在 get 方法中,先进行依赖收集
  • 然后获取属性值 res
  • 如果 res 是一个对象,则调用 reactive(res) 对其进行代理
  • 返回代理后的对象

这样,当我们访问嵌套属性时,例如 state.address.city,会发生以下过程:

  • 访问 state.address,触发 state 的 get 方法
  • 在 get 方法中,获取 address 属性,发现它是一个对象,调用 reactive(address) 创建代理
  • 返回 address 的代理对象
  • 访问 address 代理对象的 city 属性,触发 address 代理对象的 get 方法
  • 在 get 方法中,获取 city 属性,它不是对象,直接返回

通过这种递归的方式,Vue 实现了对嵌套对象的深度代理。
 

// 深度监听
effect(() => {document.body.innerHTML = state.address.city
})setTimeout(() => {state.address.city = "上海"
}, 1000)

在这个例子中,我们创建了一个 effect,它依赖于 state.address.city。当我们修改 state.address.city 时,effect 会重新执行,更新页面内容。

这是因为 state.address 是一个代理对象,当我们访问 state.address.city 时,会触发 state.address 的 get 方法,从而收集依赖。当我们修改 state.address.city 时,会触发

state.address 的 set 方法,从而触发依赖更新。

bind 方法的应用

在 Vue 的响应式系统中,bind 方法被用于创建 effect 的运行器(runner)

export function effect(fn, options: any = {}) {// 创建一个 effect 只要依赖的属性变化,就会重新执行const _effect = new ReactiveEffect(fn, () => {_effect.run();});// 执行_effect.run();if (options) {Object.assign(_effect, options); //用用户传入的配置,来覆盖默认的配置}const runner = _effect.run.bind(_effect); //bind 改变this指向 并且返回一个函数runner.effect = _effect; //将effect挂载到runner上return runner;
}

bind 方法的作用

bind 方法是 JavaScript 中函数对象的一个方法,它创建一个新函数,该函数的 this 被绑定到指定的值。在 Vue 的响应式系统中, bind 方法的作用是:

  • 创建一个新函数 runner,它的 this 被绑定到 _effect
  • 这样,无论在哪里调用 runner,它内部的 this 都指向 _effect
  • 这确保了 runner 可以正确地访问 _effect 的属性和方法

为什么需要 bind?

在 JavaScript 中,函数的 this 值取决于函数的调用方式,而不是函数的定义方式。如果我们直接返回 _effect.run,那么当调用这个函数时,this 可能不指向 _effect,导致错误。

通过使用 bind,我们确保了无论如何调用 runner,它内部的 this 都指向 _effect,从而保证了函数的正确执行。

总结

本文深入探讨了 Vue 响应式系统的几个高级特性:

  1. 自定义调度器:通过调度器,我们可以控制 effect 的执行时机和方式,实现更灵活的响应式行为。这是 AOP(面向切面编程)思想在 Vue 中的一个应用。
  2. 递归调用规避:Vue 通过在 effect 执行前后设置标志位,避免了在 effect 中修改响应式数据导致的递归调用问题。
  3. 深度代理:Vue 通过在 getter 中递归调用  reactive 函数,实现了对嵌套对象的深度代理,使得无论访问对象的哪一层属性,都能触发依赖收集和更新。
  4. bind 方法的应用:Vue 使用  bind 方法创建 effect 的运行器,确保了无论在哪里调用运行器,它内部的 this 都指向正确的 effect 实例。

通过理解这些高级特性,我们可以更深入地理解 Vue 响应式系统的工作原理,以及如何利用这些特性构建更高效、更灵活的 Vue 应用。

在实际开发中,虽然我们可能不需要直接操作这些底层 API,但了解它们的工作原理,可以帮助我们更好地理解 Vue 的响应式系统,以及在遇到复杂问题时进行调试和优化。

    相关文章:

  1. 【Linux】socket网络编程之UDP
  2. 主场景 工具栏 植物卡牌的渲染
  3. 使用adb设置wifi相关
  4. 《100天精通Python——基础篇 2025 第16天:异常处理与调试机制详解》
  5. SpringCloud服务拆分:Nacos服务注册中心 + LoadBalancer服务负载均衡使用
  6. LeetCode 热题 100 131. 分割回文串
  7. 【QT】: 初识 QWidget 控件 | QWidget 核心属性(API) | qrc 文件
  8. 湖北理元理律师事务所:债务优化中的“生活保障”方法论
  9. 软件逆向工程核心技术:脱壳原理与实战分析
  10. 前端开发中移动端调试的日常工具整理
  11. 《React Native性能优化:从卡顿到丝滑的蜕变之旅》
  12. 信创生态核心技术栈:数据库与中间件
  13. Vue 3.0中Treeshaking特性
  14. 迪士尼机器人BD-X 概况
  15. # 如何使用 PyQt5 创建一个简单的警报器控制界面
  16. Chroma:一个开源的8.9B文生图模型
  17. 【LunarVim】CMake LSP配置
  18. 人协同的自动化需求分析
  19. 【SQLSERVER】Ubuntu 连接远程 SQL Server(MSSQL)
  20. 搭建和优化CI/CD流水线
  21. 招商蛇口:今年前4个月销售额约498.34亿元
  22. 上海:企业招用高校毕业生可享受1500元/人一次性扩岗补助
  23. 经济日报整版聚焦“妈妈岗”:就业路越走越宽,有温度重实效
  24. 三大交易所多举措支持科创债再扩容,约160亿证券公司科创债有望近期落地
  25. 潘功胜发布会答问五大要点:除了降准降息,这些政策“含金量”也很高
  26. 抗战回望20︱《山西省战区抗敌行政工作检讨会议议决案》:“强民政治”、“说服行政”