跳到主要内容

Vue2

2025年01月04日
柏拉文
越努力,越幸运

一、vue.js 2.0


1.1 响应式

Vue 2.0 中通过引入 VNodediff 算法去解决 1.x 中的问题。将 watcher 的粒度放大,变成一个组件一个 watcher(就是我们说的渲染 watcher),这时候你页面再大,watcher 也很少,这就解决了复杂页面 watcher 太多导致性能下降的问题。

在组件渲染的过程中, 用到的数据属性会触发 getter, getter 内部会收集依赖。当依赖发生改变,触发 setter,则会通知watcher,从而使关联的组件重新渲染。这时候问题就来了,Vue 1.xwatcherkey 一一对应,可以明确知道去更新什么地方,但是 Vue 2.0watcher 对应的是一整个组件,更新的数据在组件的的什么位置,watcher 并不知道。这时候就需要 VNode 出来解决问题。

通过引入 VNode,当组件中数据更新时,会为组件生成一个新的 VNode,通过比对新老两个 VNode,找出不一样的地方,然后执行 DOM 操作更新发生变化的节点,这个过程就是大家熟知的 diff

Vue 响应式的实现过程如下:

  1. Vue 初始化的过程中, 调用 initState 初始化响应式数据, 调用 observedata 添加响应性

  2. 遍历 data 对象所有属性, 调用 defineReactive 为每个属性添加响应性:

    1. 为每个属性实例化 Dep

    2. 每个属性继续调用 observe 尝试为后代属性添加响应性, observe 函数中会判断属性类型, 只有对象或者数组才会继续添加响应性

      • 如果 data[xx] 为对象: 继续循环遍历 data[xx] 对象中的所有属性, 递归

      • 如果 data[xx] 为数组: 重写 data[xx] 中的 pushpopshiftunshiftsplicesortreverse 等七个可以原地改变数组的方法, 然后调用 observeArray 遍历数组, 为每个元素调用 observe 添加响应性

    3. 为每个属性通过 defineProperty 添加 setter/getter 拦截函数, 后续在访问或者设置值时可以拦截

  3. 访问数据, 触发 getter 函数: 如果当前 Watcher 存在的话, 调用 dep.depend() 进行依赖收集, 当前 WatchernewDeps 存储当前 dep , 当前 depsubs 存储当前 Watcher

  4. 设置数据值, 触发 setter 函数: 调用 dep.notify() , 循环遍历 subs 中的所有 Watcher, 执行 Watcherupdate 方法。

  5. update 方法中, 会将此时的 Watcher 加入到渲染队列 queue, 通过 nextTick 进行批量更新渲染

  6. nextTick 中,通过 promise.then(flushCallbacks) 将批量更新任务放到了微任务队列, 依次执行任务队列中的任务, 每一个任务就是一个 Watcher, 开始执行每一个 Watcherrun 方法

  7. Watcher 中的 run 方法调用 Watcher 中的 get 方法, get 方法调用 Watcher 中的 getter 函数, 此时的 getter 函数就是 updateComponent , 用于初始或者更新渲染

1.2 Virtual DOM

Vue2 引入了 VNodediff 算法,将组件 编译 成 VNode,每次响应式数据发生变化时,会生成新的 VNode,通过 diff 算法对比新旧 VNode,找出其中发生改变的地方,然后执行对应的 DOM 操作完成更新。