Vue底层实现原理概述

前言

最近在研究 剖析Vue原理&实现双向绑定MVVM 这篇文章,一边学习一边总结一下自己的思考。

Vue是一个典型的MVVM框架,模型(Model)只是普通的JavaScript对象,修改它则视图(View)会自动更新。这种设计让状态管理变得非常简单而直观。那么Vue是如何把模型和视图建立起关联的呢?

实现原理概述

这是前言提到的文章里的代码,一段典型的体现了Vue特点的代码:

<div id="mvvm-app">
    <input type="text" v-model="word">
    <p>{{word}}</p>
    <button v-on:click="sayHi">change model</button> //点击这个button,word的值会发生改变
</div>

<script src="./js/observer.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compile.js"></script>
<script src="./js/mvvm.js"></script>
<script>
    var vm = new MVVM({
        el: '#mvvm-app',
        data: {
            word: 'Hello World!'
        },
        methods: {
            sayHi: function() {
                this.word = 'Hi, everybody!';
            }
        }
    });
</script>

Vue实现这种数据双向绑定的效果,需要三大模块:

  1. Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  2. Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  3. Watcher:作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

Observer

Observer的核心是通过Obeject.defineProperty()来监听数据的变动,这个函数内部可以定义settergetter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

Watcher

Watcher订阅者作为ObserverCompile之间通信的桥梁,主要做的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加自己
  2. 自身必须有一个update()方法
  3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile

Compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

总结

关于每部分具体的代码实现,可以参阅 剖析Vue原理&实现双向绑定MVVM 这篇文章,这篇文章写得非常好。我打算研究透彻之后自己实现一下简易的Vue框架。

<think>好的,我现在需要详细解释Vue3中watch的底层原理。首先,我得回顾一下Vue3的响应式系统,尤其是基于Proxy的实现。因为watch的机制和响应式系统紧密相关。 用户可能已经知道Vue3用Proxy代替了Vue2的Object.defineProperty,所以首先要从响应式数据如何被追踪变化说起。当创建一个响应式对象时,Vue会使用Proxy来包装目标对象,拦截get和set等操作。这样,当访问属性时,会收集依赖(即effect),当属性修改时,触发这些依赖。 接下来,watch的实现依赖于这个响应式系统。当使用watch监视一个响应式对象时,Vue会创建一个effect,这个effect会在监视的源发生变化时执行回调函数。需要说明effect是如何被注册和调度的。 然后,要区分watch的不同用法,比如监视一个ref,一个reactive对象,或者一个函数返回的值。不同情况下,Vue内部的处理可能不同。例如,如果源是一个函数,Vue会在effect中执行这个函数,从而访问到响应式属性,进而收集依赖。 还要提到watch的深层监视(deep选项)是如何实现的。对于对象或数组,Vue会递归地追踪所有嵌套属性,这样任何层次的改变都能触发回调。这可能涉及到递归地使用Proxy或者遍历对象属性来收集依赖。 另外,flush选项(如pre,post,sync)控制回调的执行时机,这涉及到任务调度机制。Vue的调度器会将回调放入微任务队列或同步执行,这需要结合JavaScript的事件循环来解释。 可能还需要对比watchEffect,说明它们的异同。watchEffect会立即执行传入的函数并自动追踪依赖,而watch需要显式指定源,并且默认不会立即执行。 最后,要考虑到性能优化,比如如何避免不必要的重复执行,或者如何清理副作用。比如在组件卸载时,Vue会自动停止watch,防止内存泄漏。 需要注意的是,用户可能需要具体的代码示例或流程图来理解整个过程。但根据问题,用户希望的是底层原理,所以应该深入Vue的源码结构,比如提到ReactiveEffect类,以及如何将watch的源转换为getter函数,进而创建effect实例。 可能存在的疑问点包括:依赖收集的具体过程,effect的触发时机,以及如何实现异步回调的执行。需要确保这些点都被覆盖,同时用易于理解的语言表达,避免过于晦涩的技术术语,但又要准确。 总结起来,回答的结构应该是: 1. Vue3响应式系统概述(Proxy和effect)。 2. watch的基本工作原理:创建effect,依赖收集,触发回调。 3. 不同源的处理方式(ref、reactive、getter函数)。 4. 深度监视的实现。 5. 回调的调度机制(flush选项)。 6. 和watchEffect的区别。 7. 清理副作用和性能优化。 在解释过程中,需要确保逻辑清晰,逐步深入,并且回答正确无误。如果有不确定的地方,比如具体的源码实现细节,应该参考Vue的官方文档或源码,确保准确性。</think>在 Vue3 中,`watch` 的底层实现基于其**响应式系统**和**副作用调度机制**。以下是其核心原理的分步解析: -- ### 1. **响应式系统基础** Vue3 使用 `Proxy` 代理对象,通过 `Reflect` 实现依赖收集(track)和触发更新(trigger): - **依赖收集**:当访问响应式对象的属性时,触发 `getter`,将当前运行的副作用(`effect`)注册到依赖中。 - **触发更新**:当修改属性时,触发 `setter`,通知所有关联的副作用重新执行。 --- ### 2. **watch 的核心流程** 当调用 `watch(source, callback)` 时,Vue 会执行以下操作: #### **步骤 1:创建 Getter 函数** - 根据 `source` 类型生成对应的 `getter` 函数: - 若 `source` 是 **ref**:`() => source.value` - 若 `source` 是 **reactive 对象**:`() => source`(结合 `deep` 选项递归追踪) - 若 `source` 是 **函数**:直接使用该函数,通过执行它访问响应式数据以收集依赖。 #### **步骤 2:创建 Effect 副作用** - 使用 `getter` 创建一个 **响应式 effect**(`ReactiveEffect` 实例): ```javascript const effect = new ReactiveEffect( getter, () => scheduler(effectRunner) // 调度器控制回调执行时机 ) ``` - **首次执行** `effect.run()`,运行 `getter` 并收集依赖(将当前 `effect` 与响应式数据关联)。 #### **步骤 3:依赖变化触发回调** - 当 `getter` 中的响应式数据变化时,触发 `effect` 重新运行。 - **新旧值对比**:再次执行 `getter` 获取新值,若新旧值不同(或 `deep` 模式下结构变化),触发 `callback(newValue, oldValue)`。 --- ### 3. **深度监听(deep: true)** - **递归追踪**:通过遍历对象/数组的所有属性,并主动访问每个属性,触发它们的 `getter` 以收集深层依赖。 - **性能优化**:Vue3 使用 `traverse` 函数递归访问嵌套属性,但会通过缓存避免重复追踪。 --- ### 4. **调度控制(flush 选项)** - **flush: 'pre'**(默认):将回调放入微任务队列,在 DOM 更新前执行。 - **flush: 'post'**:在 DOM 更新后执行(类似 `nextTick`)。 - **flush: 'sync'**:同步立即执行。 --- ### 5. **与 watchEffect 的区别** | 特性 | watch | watchEffect | |--|-------------------------------| | **依赖收集** | 显式指定源(source) | 自动追踪函数内所有响应式依赖 | | **立即执行** | 默认不立即执行(可配 `immediate`) | 立即执行并收集依赖 | | **新旧值** | 提供新旧值对比 | 无 | -- ### 6. **源码关键逻辑(简化版)** ```javascript function watch(source, callback, options) { const getter = isFunction(source) ? source : () => traverse(source) // 处理 deep let oldValue const job = () => { const newValue = effect.run() if (hasChanged(newValue, oldValue)) { callback(newValue, oldValue) oldValue = newValue } } const effect = new ReactiveEffect(getter, scheduler) oldValue = effect.run() // 首次收集依赖 } ``` --- ### 总结 Vue3 的 `watch` 通过 **响应式 effect** 和 **调度器** 实现数据监听,其底层依赖 Proxy 的依赖收集/触发机制,结合递归遍历(`deep`)和任务队列(`flush`)控制,提供了灵活的数据监听能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值