从一些问题开始
- Vue 响应式概念
- Vue 如何实现响应式系统
- 如何收集依赖并通知更新
- 如何实现数组和对象的响应
概念
响应性是指当数据模型发生变化时,相关视图都会更新,而不需要开发者手动更新视图
如何实现
Vue 在初始化阶段会递归遍历data
选项,并使用Object.defineProperty
将每个property
转换为getter/setter
。当在运行时阶段访问该属性时,将触发 getter 来收集依赖项。当属性发生变化时,会触发setter通知更新。
如何收集依赖并通知更新
Vue 前往每个 property
并调用 defineReactive
,其功能是定义 getter/setter
。首先创建一个 Dep 实例对象,它与当前属性一一对应,代表一种依赖关系。
每个组件实例对应一个Watcher实例。当一个组件挂载或者更新的时候,会访问任意一个属性,触发对应的getter并调用dep.depend()
,目前watcher会将每个dep依次添加到自己的依赖队列中newDeps
(这里是一个新的Deps,也就是说它是另一个deps,下面会解释它的作用)同时,每个dep也会将watcher添加到自己的订阅者队列中subs
。更新完成后,依赖将被移除,新的Deps将提供给deps。
当某个属性发生变化时,会调用其对应的dep.notify
来通知subs
中的所有watcher进行更新。
还没结束。当组件更新时,会触发新一轮的依赖收集。更新完成后,会调用依赖清理,删除无用的依赖。什么是无用的依赖?比如一开始显示的数据由于v-if
等原因没有显示出来。即使此时数据已更改,组件也不应更新。那么它是如何实现的呢?
watcher 在示例中有两个队列:newDeps
和deps
。分别存储上次收集的依赖项和上次收集的依赖项。更新后,您会发现依赖项位于 deps 中,而不位于 newDeps 中。前面提到,依赖部门存储了相关的watcher来通知更新。此时,这个watcher就应该被移走,一旦改变,watcher就不会收到更新通知了。清理完成后,将newDeps
分配给deps
,并移除newDeps
以等待下一次依赖收集。
举个例子来说明为什么要删除依赖:
假设当前组件对应的watcher实例为w,在组装之初就打开了baz,因此会收集对baz的依赖,当baz发生变化时会通知w更新;当 show 更改为 false 后,会触发新一轮的更新和依赖收集。目前 baz 不会被打开,因此 baz 部门收集的 w 将被删除。 baz变更时,不会通知我们更新。
<template>
<div>
<div>{{foo}}</div>
<div v-if="show">{{baz}}</div>
</div>
</template>
<script>
export default {
data () {
return {
foo: 1,
baz: 'a',
show: true
}
},
mounted () {
this.show = false
}
}
</script>
如何实现数组和对象的响应
对于矩阵
Vue无法检测到通过写入的索引直接设置数组元素,并改变长度属性。这是因为响应式数组攻丝带来的性能问题与收益不成正比
解决方案是vue封装数组的change方法,这样就可以触发视图更新。在内部,它调用整个数组的属性对应的dep的notify方法来通知更新。
对于对象
Vue 无法检测属性的添加或删除。然后使用 Vue.set(object, propertyName, value)
方法手动定义反应性属性。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。