vue2响应式原理

851 阅读3分钟

概述

响应式数据的目的,就是当数据、对象属性、数组某项发生改变时,能够自发的运行某些函数。在 vue 中最常见的就是数据发生改变时重新运行 render 函数刷新页面。

vue 中实现响应式主要依靠一下几个核心部分

  1. Observer
  2. Dep
  3. Watcher
  4. Scheduler

Observer

Observer 要实现的功能很简单,就是将一个普通的数据变为响应式数据

  • 对于对象:是采用递归调用 Object.defineProperty 来对对象进行劫持,为每个属性添加 getter 与 setter,这样在请求属性或者更改时 vue 能够收到通知来做一些事情。但即使是这样我们还是无法监听到对象属性的增加与删减,因此 vue 提供了 $set$delete 实例方法,来对响应式数据进行属性的增减
  • 对于数组,vue 会更改数组的隐式原型,将其变为 vue 自定义的对象,对象中对原先一些能改变数组的方法进行了重写,这样 vue 就能监听到数组内容的变化,最后将将自定义对象的隐式原型指向 Array.prototype
graph LR
A(数组) -- __proto__ --> B(vue自定义对象)
B -- __proto__ -->C(Array.protoType)

Dep

到这里数据已经变为响应式数据了但我们根本不知道读取属性时、修改属性时该做什么事情。而 Dep 就是来解决这个事情的 vue 会为每个响应式数据生成一个 Dep 实例,Dep 实例会做两件事

  1. 记录依赖:当有人访问这个数据时,把它记录下来
  2. 派发更新:当数据被修改时通知所有的依赖数据更改了
graph LR
A(getter) -- 记录依赖 --> B(someone use me)
C(setter) -- 派发更新 -->D(I have been changed)

Wather

现在我们知道当访问一个响应数据时,Dep 会纪录依赖,但 Dep 该怎么知道到底是谁访问了我呢

Watcher 就是用来解决这个问题的

当某个函数运行时用到了响应数据,Dep 是没办法知道到底哪个函数在使用自己 因此我们将这个函数交给 Watcher 来执行。Watcher 会设置一个全局的变量来记录当前执行的 Watcher,当发生 Dep 记录依赖时,Dep 就将这个全局变量记录下来,表示当前这个 Watcher 用到了自己。当数据改变 Dep 派发更新时就会通知 Watcher 我变了,Watcher 就会重新调用对应的函数

每一个 vue 实例都至少有一个 Watcher 实例来保存 render 函数,当 vue 组件首次渲染时 Watcher 就会调用 render 函数来收集依赖

所有在 render 中用到的响应式数据都会纪录这个 Watcher

当数据发生改变时,Dep 会通知 Watcher,Wather 就会重新运行 render 函数,更新依赖。

Scheduler

此时 vue 响应式已经基本完成,但仍然存在一些问题。

当 render 中用到了许多响应式数据,如果这些响应式数据都发生了变化,那么记录 render 的 Watcher 会被重复调用多次,浪费了许多性能。这显然是不合理的。

实际上 Watcher 并不会立即执行而是会把自己交给 Scheduler ,Scheduler 会维护一个队列,队列中同一个 Watcher 只能存在一次,vue 内部提供了 nextTick 方法,可以把这些 watcher 放入到事件循环中的微队列中,所以 Watcher 的执行都是异步的

详细流程图

vue响应式.png