Skip to content

VUE2 的响应式原理和流程

在 Vue 2 中,响应式系统的依赖于 Object.defineProperty() 。Vue 在初始化实例时对 data 对象的每个属性进行处理,转换成 getter/setter,并收集依赖,以便在属性被访问和修改时通知变化。

Vue2 的响应式原理主要通过数据劫持和依赖收集实现。核心流程包括:初始化数据、依赖收集、数据变更检测、派发更新和异步更新队列。

  1. 初始化阶段 在创建一个 Vue 实例时,Vue 会调用 initData() 函数来初始化 data 对象的所有属性。通过 defineReactive() 方法,Vue 使用 Object.defineProperty 将每个属性转换为 getter/setter 形式,从而实现对数据的劫持。

在这个过程中,Vue 会递归遍历 data 对象的所有嵌套属性,将每个属性都转换为 getter/setter。这样,当页面中访问或修改这些属性时,就可以触发相应的操作。

  1. 依赖收集

Vue 使用 Dep(依赖管理类)来维护依赖关系。每个响应式属性都会对应一个 Dep 实例。当组件在渲染过程中访问数据时,会触发属性的 getter 方法,并在 getter 中进行依赖收集。具体流程如下:

  • Watcher 实例:在组件渲染时,Vue 会创建一个 Watcher 实例,将其作为当前正在计算的依赖。
  • 依赖收集:当数据的 getter 被触发时,当前的 Watcher 会被添加到 Dep 的依赖列表中。这样,一旦数据变化,Dep 可以通过这个依赖列表通知相关的 Watcher,从而实现数据驱动的视图更新。
  1. 数据变更与派发更新

当数据发生变化时,属性的 setter 会被触发。在 setter 中,新的值会被存储,同时触发 dep.notify() 方法。dep.notify() 会遍历依赖列表中的所有 Watcher,并调用它们的 update() 方法,通知这些依赖进行更新。

  1. 异步更新队列

为了提高性能,Vue 使用异步队列来控制 DOM 更新的时机。在同一事件循环中,无论数据属性被修改多少次,Vue 只会在下一个事件循环中执行一次真正的 DOM 更新。具体机制如下: • nextTick():当数据变化后,Vue 会将 Watcher 加入更新队列,并在异步队列中等待。在同一事件循环结束后,nextTick() 和 flushSchedulerQueue() 会确保 DOM 更新只执行一次,从而避免多次渲染带来的性能问题。

此外,$nextTick() 方法允许我们在数据变化后的下一个 DOM 更新循环中执行回调,非常适合立即操作已更新的 DOM。

Vue2 响应式的局限性

Vue2 的响应式系统只能监听对象已有属性的变化,不能直接检测到对象属性的新增或删除。同时,对数组的响应式支持较为有限,无法直接监听数组元素的新增或删除操作。

为了解决这些问题,Vue 提供了 Vue.set() 和 Vue.delete() 方法来动态添加/删除对象的响应式属性。对于数组操作,可以使用 JavaScript 的原生数组方法(如 push()、pop()、splice())来触发响应式更新。

VUE3 的响应式原理

在 Vue3 中,响应式系统进行了重构,采用了 Proxy 代理和全新的 Composition API,大大提升了性能和灵活性。Vue3 的响应式核心流程包括:数据代理、依赖收集、变更检测和异步更新。以下是 Vue3 响应式原理的说明:

  1. 数据代理与初始化

Vue3 使用 Proxy 代理代替了 Vue2 中的 Object.defineProperty,这使得 Vue3 能够直接代理整个对象,无需递归遍历每个属性。Proxy 可以直接监听属性的动态添加、删除和嵌套属性的变化,并提供更细粒度的控制。关键步骤如下: • reactive():Vue3 提供了 reactive() 函数,将一个对象转换为响应式对象。通过 Proxy 拦截对象的 get 和 set 操作,实现对对象和其嵌套属性的响应式代理。 • ref():对于基本数据类型,Vue3 提供了 ref() 函数,使得单个基本数据类型也可以成为响应式数据。

  1. 依赖收集

Vue3 依然通过 Dep 实现依赖管理,但 Vue3 重构后的依赖收集机制更加灵活、可控。具体过程如下: • Effect(副作用):Vue3 引入了 effect 函数,将需要响应式更新的逻辑包裹起来。每当 effect 中访问响应式数据时,都会触发 track 函数进行依赖收集。 • track():当访问响应式数据的 get 方法时,track() 会记录当前的 effect,并将其添加到对应属性的依赖列表中,从而实现依赖收集。 • targetMap:Vue3 通过一个 targetMap 对象来记录每个响应式对象及其属性的依赖关系,依赖树更加优化,内存消耗更低。

  1. 数据变更与派发更新

在 Vue3 中,当响应式数据发生变化时,会触发 Proxy 的 set 拦截操作,从而调用 trigger 函数来派发更新。主要步骤如下: • trigger():当数据发生变化时,trigger() 函数会找到对应属性的依赖列表,并通知所有的 effect 执行更新。 • Scheduler:Vue3 的 trigger 方法支持调度器(Scheduler),可以控制 effect 的执行顺序和频率,使得复杂情况下的更新更高效。

  1. 异步更新与调度

Vue3 延续了 Vue2 中的异步更新机制,依旧通过 nextTick() 和异步任务队列来控制 DOM 更新的时机,但提供了更灵活的调度方案: • Scheduler Queue:Vue3 引入了全新的调度系统,允许开发者在触发响应式更新时使用自定义的调度器,优化了复杂场景下的更新流程。 • nextTick():Vue3 的 nextTick() 用于在下一个 DOM 更新循环之后执行回调函数,帮助开发者精确地控制 DOM 操作的时机。

  1. Vue3 响应式系统的优势

    • 支持动态属性和嵌套对象:由于使用了 Proxy,Vue3 可以直接监听动态添加或删除的属性,以及深层嵌套的对象。 • 更细粒度的响应性:Vue3 的 effect 和 ref 使得响应式系统更具扩展性,能够支持组件复用和函数式编程风格。 • 提升了性能:Vue3 的响应式系统在内存占用、依赖追踪和更新调度等方面进行了优化,特别是在大规模数据操作和复杂组件树中表现更出色。

Vue3 与 Vue2 的响应式区别

1.	代理机制:Vue2 使用 Object.defineProperty,只能监听已有属性;Vue3 使用 Proxy,可以动态监听属性的添加、删除及嵌套对象的变化。
2.	依赖管理方式:Vue3 使用了 targetMap 和 effect 来进行依赖管理,相比 Vue2 的 Dep 更具扩展性和性能优势。
3.	调度系统:Vue3 的响应式更新引入了调度器,支持自定义的更新策略,在复杂场景中优化了渲染效率。

响应式 API

ref():接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value。

ref 对象是可更改的,也就是说你可以为 .value 赋予新的值。它也是响应式的,即所有对 .value 的操作都将被追踪,并且写操作会触发与之相关的副作用。如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。若要避免这种深层次的转换,请使用 shallowRef() 来替代。

reactive():返回一个对象的响应式代理。