前言

最近在学习 Vue 的源码,Vue 响应式原理在面试中也是经常会被问到的一个问题,在跟着慕课网黄老师学习源码的过程中,发现源码有点难懂(哭)。最近在网上看了几片介绍响应式原理的文章,在这里以一种通俗易懂的方式记录一下。

什么是响应式原理

我们都知道,Vue.js 是由数据驱动 UI 变化的框架,也即是说,数据发生改变的时候,视图会重新渲染,匹配更新为最新的值。这就是响应式,只要在 Vue 实例中声明过的数据,那么这个数据就是响应式的。
我们可以提出三个问题:

  1. Vue 是怎么知道数据改变?
  2. Vue 在数据改变时,怎么知道通知哪些视图更新?
  3. Vue 在数据改变时,视图怎么知道什么时候更新?
    在 Vue 中对应着三个问题的解决方案关键词分别是
  4. Object.defineProperty
  5. 依赖收集
  6. 派发更新。
    我们一一解释这三个关键词。

Object.defineProperty

这个方法,是 Vue 响应式系统的精髓,骨髓,脑髓(核心呐!)
使用 Object.defineProperty 可以为对象中的每一个属性,设置 get 和 set 方法。
get 和 set 又他喵是什么鬼?
get 值是一个函数,当属性被访问时,会触发 get 函数
set 值同样是一个函数,当属性被赋值时,会触发 set 函数
现在可以解答第一个问题了:
在 Vue 中,属性的 set 方法被做了手脚,当数据改变时,触发属性的 set 方法,便能知道数据发生了改变。

依赖收集

简单的说,data 中的每一个属性都有一个数组,保存着谁依赖(使用)着他。
比如页面 A 中的<div></div>,此时页面 A 就存在于 name 的后宫中,实际上,会依赖 name 的地方,不只是页面,还会有 computed,watch 等等。这就是依赖收集,把依赖了我(使用了我的东西),统统保存起来。

实际上,在 Vue 中,name 属性,使用了 一个 dep 保存了页面 A 这个依赖,而保存的实际上是页面 A 的 Watcher。
每个 Vue 实例都会拥有一个专属的 watcher,可用于实例更新。

总结一下:

  1. data 中每个声明的属性,都会有一个 专属的依赖收集器 subs
  2. 当页面使用到某个属性时,页面的 watcher 就会被放到依赖收集器 subs 中

那么什么时候进行依赖收集呢?
那就是通过上面讲的 get 函数啦!当页面读取了 name 时,就会触发 name 的 get 函数,此时 name 就会保存页面的 watcher 了~

派发更新

派发更新就是在数据改变之后通知所有的依赖进行更新,上面我们了解到每一个页面都会保存一个依赖收集器 subs,这个依赖收集器就是用来在数据变化时通知更新的。

啥时候派发更新呢?
还是以上面的例子
当 name 改变的时候,name 会遍历自己的依赖收集器 subs,逐个通知 watcher,让 watcher 完成更新
这里 name 会通知页面 A,页面 A 重新读取新的 name ,然后完成渲染

总结

  1. Object.defineProperty - get ,用于 依赖收集
  2. Object.defineProperty - set,用于 依赖更新
  3. 每个 data 声明的属性,都拥有一个的专属依赖收集器 subs
  4. 依赖收集器 subs 保存的依赖是 watcher
  5. watcher 可用于 进行视图更新