这一篇笔记的重点在于探究 Vue 的响应式特性,并动手实现一个简易的响应系统。
什么是响应式
在前端框架这个语境下是指,状态的改变引起相应的 DOM 随之改变。
我们先从一个最简单的情形说起,假如有 2 个变量 a 和 b,我们要实现的需求是,让变量 b 的值总是变量 a 的 10 倍。
1 | let a = 3; |
上面代码中,每次变量 a 发生变化,必须手动更新变量 b 的值,才能使得两者始终保持 10 倍的关系。如何声明式地表示这种关系呢?
在 Excel 中,我们是通过函数来实现类似效果的:
| No. | A | B |
|---|---|---|
| 1 | 4 | 40(fx = A1 * 10) |
在程序语言中,我们想要的是一个类似这样的函数:
1 | onAChanged(() => { |
每当变量 a 的值发生改变,作为回调函数参数的这个更新函数便执行(即上面代码中的箭头函数),这样问题就解决了。那么如何实现这样一个函数?
先将前面问题稍微转化一下,使之更符合 Web 开发的实际情况。
我们现在有一个 <span> 标签 b1,它的值是状态变量 a 的 10 倍:
1 | <span class="cell b1" /> |
1 | onStateChanged(() => { |
上面的代码实际上声明式地表达了状态与 DOM 之间的关系,不过我们可以更进一步抽象成这样:
1 | <span class="cell b1"> {{ state.a * 10 }} </span> |
1 | onStateChanged((state) => { |
从上面的代码我们隐约看到了一个 UI 库的雏形,最关键的是这行代码:view = render(state),它实际上高度抽象地概括了现代前端框架存在的根本原因:通过一种映射,将应用程序的 UI 与状态同步。这里面涉及到很多虚拟 DOM 和原生 DOM 的细节,所以我们这里先关注外面的回调函数即 onStateChanged 是如何实现的。
它可能是这样实现的:
1 | let update; |
上面代码中,我们将 update 函数保存在某处,同时要求用户总是通过调用一个函数 setState 来更新状态,而不是任意地操作状态。setState 函数所做的工作如下:将旧状态替换为新状态,然后调用 update 函数。这一过程与 React 的响应式系统很像,在应用状态改变时要求用户手动调用 setState 函数:
1 | onStateChanged(() => { |
而在 Vue 中,用户可以直接操作状态,状态改变会自动触发 onStateChanged 内更新函数的执行,不再需要用户手动调用 setState。这是通过 ES5 的全局 API Object.defineProperty() 来使得状态具有响应特性。
Getter / Setter
先热下身,利用 Object.defineProperty() 动手实现一个 convert() 函数,需求如下:
- 接受一个对象类型的参数
- 将该对象的属性就地转化为
getter/setter - 该对象应该保持原有的行为,同时在被访问和修改时打印
get/set操作
1 | // 实现 |
Dependency Tracking
很明显,以上所做的还不能实现预期的效果,我们还需要利用发布/订阅模式实现 Dependency Tracking 依赖追踪。
需求如下:
- 创建一个
Dep类,这个类有两个方法depend()和notify - 创建一个
autorun函数,它接受一个update更新函数作为参数 - 在
update更新函数内部,你可以通过调用dep.depend()显式地依赖一个Dep类的实例 - 在这之后,你可以通过调用
dep.notify()使得update更新函数再次触发被调用
1 | // 实现 |
Mini Data Observer
将前面实现的 convert() 和 autorun() 这 2 个函数结合起来,同时将 convert() 函数重命名为 observe()。实现的需求如下:
observe函数接受一个对象类型的参数,并将该对象的所有属性转化成响应式的。每个被转化的属性都被分配到一个 Dep 实例,该实例跟踪着一个订阅了更新函数的列表,每当某个属性的setter被调用,就重新调用订阅的更新函数。autorun函数接受一个update函数作为参数,同时在update函数所订阅的属性被修改时,update函数将被触发执行。如果update更新函数在执行期间依赖于某个属性,则该更新函数订阅了该属性。
1 | <div> |
上面的代码实现了一个简单的 Data Observer,同时需要指出,有一些边缘情况没有考虑在内,比如清除陈旧的依赖、对数组的处理、对新添加属性的处理。