Vue2核心原理(简易版)-依赖收集
Vue的响应式数据原理
-
如果你仔细的阅读官方文档,会发现这么一句话:
Vue 最独特的特性之一,是其非侵入性的响应式系统。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。
这句话看似很牛逼,但其实什么也没有说😓。
当你修改它们时,视图会进行更新。但是你应该对这句话很敏感,可是怎么更新啊?哈哈,仔细思考一下,前两节课(前两篇文章响应式、模版编译)的内容是不是帮我们实现了以下两点:- 我们通过对数据的观测(observe),知道了数据是什么时候修改的,也就是什么时候应该更新了。
- 视图通过模版编译的方式去更新dom节点。
-
那么如果让你说一下对响应式数据的理解呢?
注意,这里并不仅仅指的是Vue.js中的响应式数据。我个人认为:数据在产生变化(读、写)过程中,能够通知到我们它的行为,并且让我们有针对这些行为做出一系列的应对的能力,这样的数据被称为响应式数据。
好,那么我们现在回到Vue.js,怎样让具备在数据变化时有对其进行反应的能力?其实质就是核心的defineReactive,落实到js里面就是Object.defineProperty。再者,什么是通知呢?在上一篇完成的代码里面,我们去尝试改变data模型里面的数据:<body> <p>p1 - {{name}}</p> </body> <script> let vm = new Vue({ el: '#app', data() { return { name: 'vue', } } }) setTimeout(() => { vm.name = 'vue3' }, 1000) </script>结果:
哈哈,是不是什么也没发生!不,其实我们已经知道了name从'vue'变成了'vue3',但是我们没有去通知我们的_render函数,需要重新render啦(update)。
由此一来,便有了我们今天的话题,依赖收集!
Vue的响应式数据依赖收集
更新视图
为了调用render函数,更新视图,这是我们之前的做法:
// 更新函数 数据变化后 会再次调用此函数
let updateComponent = () => {
// 调用render函数,生成虚拟dom
vm._update(vm._render()); // 后续更新可以调用updateComponent方法
// 用虚拟dom 生成真实dom
}
updateComponent();
仔细推敲不难发现,因为我们仅仅是在mount阶段调用了一次updateComponent,所以我们之后对data数据进行的各种修改,当然不会重新触发render,视图也就不会更新啦。那么我们是不是需要有一种方法,可以自动的帮我们完成这些操作!
观察者模式
lěi le lěi le:
观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。
什么意思呢?在Vue.js里,也就是说,给每一个组件页面添加一个观察者,给我们的data里的每一个需要侦听的目标都添加上这个观察者的实例,让这个观察者存储所有的依赖。这样,当依赖发生改变行为时,就可以通知(notify)给观察者。
a. 依赖的数据是观察目标
b. 视图、计算属性、侦听器这些是观察者
观察者模式的实现
我们注意到上面的那张图所描述的,render => "touch" => data.getter => Collect as Dependency。这句话的意思是,在初始阶段,把需要用到的响应式数据收集成为当前组件的依赖。这就是我们所说的依赖收集。所以到了这一步,你应该知道我们为什么要做依赖收集了把?只有依赖收集了,才能对应到那个观察者,才可以实时调用render函数,更新视图。
那么, 具体该怎么做呢?
// updateComponent();
// 观察者模式: 属性是“被观察者” 刷新页面:“观察者”
new Watcher(vm,updateComponent,()=>{
console.log('更新视图了')
},true); // 他是一个渲染watcher 后续有其他的watcher
首先就是将我们之前手动执行的updateComponent,换成是新建当前页面的Watcher实例。也就是把这个页面对应到一个观察者。
其次,每一个被模版依赖的对象/属性,都应该去订阅当前观察者。
// Object.defineProperty
get(){
// 取值时我希望将watcher和dep 对应起来
if(Dep.target){ // 此值是在模板中取值的
dep.depend() // 让dep记住watcher
// 可能是数组 可能是对象,对象也要收集依赖,
// 后续写$set方法时需要触发他自己的更新操作
if(childOb){
// 就是让数组和对象也记录watcher
childOb.dep.depend();
//取外层数组要将数组里面的也进行依赖收集
if(Array.isArray(value)){
dependArray(value);
}
}
}
return value
},
而当前观察者,也要反向记录自己的依赖项。
depend(){
// Dep.target
// dep里要存放这个watcher,watcher要存放dep
if(Dep.target){
Dep.target.addDep(this);
}
}
此时,如果依赖项发生改变,我们让它通知它的观察者们,作出反应。也就是重新render组件。
// observer/index.js => Object.defineProperty
set(newV){
if(newV !== value){
// 如果用户赋值一个新对象 ,需要将这个对象进行劫持
observe(newV);
value = newV;
// 告诉当前的属性存放的watcher执行
dep.notify();
}
}
// observer/dep.js => notify
notify(){
this.subs.forEach(watcher=>watcher.update());
}
// observer/watcher.js => update
update(){ // vue中的更新操作是异步的
// 每次更新时 this
// 多次调用update 我希望先将watcher缓存下来,等一会一起更新
queueWatcher(this);
}
详尽的流程我画了一个思维导图和一张流程图,请仔细阅读:
完 🎉
下一讲,异步更新原理