vuearray取值_⼿写Vue源码(五)-依赖收集
依赖收集
源码地址:传送门
数据更新时⾃动更新DOM。本⽂将详细介绍Vue源码中该特性实现的核⼼思路,深⼊理解Vue数Vue为⽤户提供了⼀个特别⽅便的功能:数据更新时⾃动更新
据和视图的更新关系。
思路梳理
如何追踪变化
这是Vue官⽅数据变化引发视图更新的图解:
⽤⽂字描述的话,其流程如下:
1. 组挂载,执⾏render⽅法⽣成虚拟DOM。此时在模板中⽤到的数据,会从vm实例上进⾏取值
2. 取值会触发data选项中定义属性的get⽅法
3. get⽅法会将渲染页⾯的watcher作为依赖收集到dep中
4. 当修改模板中⽤到的data中定义的属性时,会通知dep中收集的watcher执⾏update⽅法来更新视图
5. 重新利⽤最新的数据来执⾏render⽅法⽣成虚拟DOM。此时不会再收集重复的渲染watcher
渲染 watcher就是⽤来更新视图的 watcher,具体的执⾏过程在组件初渲染中有详细介绍,它的主要作⽤如下:
1. 执⾏ vm._render⽅法⽣成虚拟节点
2. 执⾏ vm._update⽅法将虚拟节点处理为真实节点挂载到页⾯中
需要注意的是,数组并没有为每个索引添加set/get⽅法,⽽是重写了数组的原型。所以当通过调⽤原型⽅法修改数组时,会通知watcher来更新视图,保证页⾯更新。
Dep
收集watcher并且在数据更新后通知watcher更新DOM的功能主要是通过Dep来实现的,其代码如下:
constructor () {
// dep的唯⼀标识
this.id = id++;
this.subs = [];
}
addSub (watcher) {
this.subs.push(watcher);
}
// 通过watcher来收集dep
depend () {
Dep.target.addDep(this);
}
/
/ 执⾏所有收集watcher的update⽅法
notify () {
this.subs.forEach(sub => {
sub.update();
});
}
}
Dep会将watcher收集到内部数组subs中,之后通过notify⽅法进⾏统⼀执⾏。
代码中还会维护⼀个栈,来保存所有正在执⾏的watcher,执⾏完毕后watcher出栈。
const stack = [];
// 当前正在执⾏的watcher
Dep.target = null;
export function pushTarget (watcher) {
stack.push(watcher);
Dep.target = watcher;
}
export function popTarget () {
stack.pop();
Dep.target = stack[stack.length - 1];
}
⽬前代码并没有⽤到栈,在之后实现计算属性时,会利⽤栈中存储的渲染 watcher来更新视图
通过上⾯的代码,就可以通过dep来实现对watcher的收集和通知。
Watcher
本⽂中讲到的 watcher只是起到渲染视图的作⽤,所以将其称为渲染 watcher。在之后涉及到 watch和 computed之后,还会有它们各⾃相对应的 watcher。
Watcher的主要功能:
收集dep,⽤于之后实现computed的更新
通过get⽅法来更新视图
constructor (vm, exprOrFn, cb, options) {
// 唯⼀标识
this.id = id++;
this.vm = vm;
this.cb = cb;
this.options = options;
this.deps = [];
this.depsId = new Set(); // 利⽤Set来进⾏去重
if (typeof exprOrFn === 'function') {
< = prOrFn;
}
<();
}
// 在watcher中对dep进⾏去重,然后收集起来,并且再让收集的dep收集watcher本⾝(this)。这样便完成了dep和watcher的相互收集 addDep (dep) {typeof array
// ⽤空间换时间,使⽤Set来存储deps id进⾏去重
if (!this.depsId.has(dep.id)) {
this.deps.push(dep);
this.depsId.add(dep.id);
// 重复的dep⽆法进⼊,每个dep只能收集⼀次对应watcher
dep.addSub(this);
}
}
get () {
// 更新视图之前将watcher⼊栈
pushTarget(this);
<();
// 视图更新后,watcher出栈
popTarget();
}
// 更新视图
update () {
<();
}
}
Watcher接收的参数如下:
vm: Vue组件实例
exprOrFn: 表达式或者函数
cb: 回调函数
options: 执⾏watcher的⼀些选项
⾸先,在组件初次挂载时,会实例化Watcher,在Watcher内部会执⾏传⼊的exprOrFn渲染页⾯:
Vue.prototype.$mount = function (el) {
// some code ...
mountComponent(vm);
};
export function mountComponent (vm) {
callHook(vm, 'beforeMount');
function updateComponent () {
vm._update(vm._render());
}
/
/ 在实例化时,会执⾏updateComponent来更新视图
new Watcher(vm, updateComponent, () => {}, { render: true });
callHook(vm, 'mounted');
}
当data选项中的值发⽣更新后,会通过ify来调⽤watcher的update,⽽watcher的update⽅法会调⽤exprOrFn即我们之前传⼊的updateComponent ⽅法,从⽽更新视图。
依赖收集
依赖收集时分别对对象和数组进⾏了不同的操作:
取值时:
对象:在对象每⼀个属性的get⽅法中,利⽤属性对应的dep来收集当前正在执⾏的watcher
数组:在Observer中,为所有data中的对象和数组都添加了__ob__属性,可以获取Observer实例。并且为Observer实例设置了dep 属性,可以直接通过array.__ob__.depend()来收集依赖。
设置值时:
对象:通过被修改属性的set⽅法,调⽤ify来执⾏收集的watcher的update⽅法
数组:通过调⽤数组⽅法来修改数组,在对应的数组⽅法更新完数组后,还会执⾏数组对应的array.__ob__.notify来通知视图更新
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论