You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
class Dep { // Dep === Dependencies
constructor () {
this.subscribers = []
}
depend() {
if (target && !this.subscribers.includes(target)) {
this.subscribers.push(target)
}
}
notify() {
this.subscribers.forEach(sub => sub())
}
}
Dep 中的subscribers存放了依赖列表,也就是target,target为当前依赖,什么是当前依赖,哪个时机来存储或执行呢,且继续往下看。
Object.defineProperty()
大家都知道 Vue 利用了 ES5 的 Object.defineProperty(),对该方法不熟悉的请戳:point_right:MDN。我们也知道 Vue 通过 遍历 data 的所有属性来为每个属性做响应式。每一个 data 的属性都有一个依赖列表(即 Dep 类),每当获取(getter)某一个属性时进行依赖收集的操作,每当某一个属性值发生变化(setter)时,便通知其对应的依赖列表。由此可以得出以下代码片段:
之前写过一篇《Vue3 设计系统之响应式》。我们知道 Vue3 并没有延续 Vue2 的设计,而是进行了重写,那么 Vue2 的设计是怎么样的,为什么要进行重写呢?知其然也要知其所以然。那这篇文章就是对 Vue2 响应式的一个梳理,只针对 data 层面进行了剖析,没有涉及 Compile 相关内容哈,各位老师请指教呀 ~
回顾 Vue 组件的基本写法:
而文档中的两句话点出了 Vue 的响应式原理:
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
每个组件实例都对应一个 Watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 Watcher,从而使它关联的组件重新渲染。
可见 Vue 的响应式有几个关键词:Object.defineProperty、依赖收集/依赖追踪(Dependencies)、Watcher。那基于这些信息我们来分析一下 Vue2 的 Reactivity。
利用观察者模式
当一个对象被修改时,则会自动通知依赖它的对象。
这便是观察者模式的一种使用方式,Vue 这里也采用了相应的设计。Talk is cheap, show me the code. 来看一个代码片段感受一下:
Subject
(主题)维护着一个依赖列表即_observers
,当Subject
有任何状态(_state
)更新时,便会自动通知到这些订阅者(_observers
),通知的方式一般是调用订阅者的一个方法,在这里就是signal
方法。从而实现简单的广播通信。Vue 则使用了 Dep 类来维护订阅者列表,跟上面例子设计思路是类似的:
Dep 中的
subscribers
存放了依赖列表,也就是target
,target
为当前依赖,什么是当前依赖,哪个时机来存储或执行呢,且继续往下看。Object.defineProperty()
大家都知道 Vue 利用了 ES5 的 Object.defineProperty(),对该方法不熟悉的请戳:point_right:MDN。我们也知道 Vue 通过 遍历 data 的所有属性来为每个属性做响应式。每一个 data 的属性都有一个依赖列表(即 Dep 类),每当获取(getter)某一个属性时进行依赖收集的操作,每当某一个属性值发生变化(setter)时,便通知其对应的依赖列表。由此可以得出以下代码片段:
我们一直在说 target,Dep 类里面使用 target 进行依赖收集,Object.defineProperty 的 getter 方法里面通过调用 Dep 的 depend 方法来进行依赖收集,那么 target 在哪里呢,它是怎么维护的呢?不得不引出
Watcher
这个概念,实际上每一个 Vue 组件实例都对应一个 Watcher 实例,将接触过的数据记录为依赖:可能还是没那么直接哈,那我们把
Dep
、Object.defineProperty
以及Wather
都串起来就比较好理解了:大家可以根据这部分代码一点一点带到三部分代码里面阅读一下,很惊讶于 Vue 的设计,值得读很多遍 ~
Vue2 响应式的不足及解决办法
前面我们理解了 Vue2 的响应式的实现,对其原理的设计可谓叹为观止。但是,Vue2 也不是没有自身的缺陷,否则也不会在 Vue3 彻底重写了不是。缺点一句话概括就是:
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
关于对象
先看一段代码片段,使用 Object.defineProperty 的方式给对象做拦截:
可见,Object.defineProperty 不能对对象属性的添加和删除进行监听,因此 Vue 也不能利用该原生方法做响应式处理。于是
Vue.set
便产生了。只需要简单的一行,就可以对新添加的属性实现响应式,Vue.delete
同理:关于数组
我们把上面代码的 name 先改成数组,看看数组的情况:
可以看到对于数组的几乎所有操作,都不能触发 set 方法。而 Vue 中使用 push 可以实现响应式,这是为啥呢?因为 Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:
结语
Vue2 做了很多的努力来帮助实现数据的响应式,但却是很繁琐的一件事情,很庆幸 ES6 对数据的拦截有了新的更新 Proxy 和 Reflect,将大大简化 Vue 本身的设计。希望大家读完这篇文章能对vue2的响应式有更进一步的理解,也许能为某些困惑的bug提供一些思路。
2021就只剩下两个月了,不知道今年还能写几篇技术博文,谁知道呢?今年的愿望清单大家实现的怎么样了呀?时间过的飞快,唯学习是我快乐 ~
The text was updated successfully, but these errors were encountered: