vue依赖收集代码分析
Posted everlose
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue依赖收集代码分析相关的知识,希望对你有一定的参考价值。
结构
Vue$3
__data:
__ob__: Observer
dep: Dep
id: 2
subs: []
subs里放置Watcher,当改变data数据时,触发对应的Dep通知其subs里的watcher进行更新。
代码
首先在 observer 的过程中会注册 get 方法,该方法用来进行「依赖收集」。在它的闭包中会有一个 Dep 对象,这个对象用来存放 Watcher 对象的实例。其实「依赖收集」的过程就是把 Watcher 实例存放到对应的 Dep 对象中去。get 方法可以让当前的 Watcher 对象(Dep.target)存放到它的 subs 中(addSub)方法,在数据变化时,set 会调用 Dep 对象的 notify 方法通知它内部所有的 Wathcer 对象进行视图更新。
function defineReactive (obj, key, val) {
/* 一个Dep类对象 */
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
/* 将Dep.target(即当前的Watcher对象存入dep的subs中) */
dep.addSub(Dep.target);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
/* 在set的时候触发dep的notify来通知所有的Wathcer对象更新视图 */
dep.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一个Watcher观察者对象,这时候Dep.target会指向这个Watcher对象 */
new Watcher();
/* 在这里模拟render的过程,为了触发test属性的get函数 */
console.log('render~', this._data.test);
}
}
Dep 和 Watcher 定义如下
class Dep {
constructor () {
/* 用来存放Wathcer对象的数组 */
this.subs = [];
}
/* 在subs中添加一个Watcher对象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Wathcer对象更新视图 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
class Watcher {
constructor () {
/* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
Dep.target = this;
}
/* 更新视图的方法 */
update () {
console.log("视图更新啦~");
}
}
Dep.target = null;
Dep 和 watcher 如何关联上?
追问:Dep.target 为什么会指向这个 Watcher 对象?
在 callHook(vm, ‘beforeMount‘) 后,进入 mount 阶段,此时初始化 Watcher
function noop (a, b, c) {}
// lifecycle.js
let updateComponent
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
vm._watcher = new Watcher(vm, updateComponent, noop)
在初始化 Watcher 的函数里调用 this.get
var Watcher = function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
this.vm = vm;
//...
this.cb = cb;
//...
this.expression = expOrFn.toString();
//...
this.getter = expOrFn;
//...
this.value = this.lazy ? undefined : this.get();
};
Watcher.prototype.get,注意 pushTarget,此时就和 Dep 发布者产生了联系,Dep 的 target 被设置为了这个 wacher,并且在每次监测对象被 get 时,就会往自身的 Dep 里推入这个 wacher。
// dep.js
export function pushTarget (_target: Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}
export function popTarget () {
Dep.target = targetStack.pop()
}
// watcher.js
Watcher.prototype.get = function get() {
pushTarget(this);
var value;
var vm = this.vm;
//...
value = this.getter.call(vm, vm);
//...
popTarget();
this.cleanupDeps();
//...
return value;
};
上文 Watcher.prototype.get 中还要注意 this.getter.call(vm, vm), 执行的其实是上文表达式里的 vm._update(vm._render(), hydrating)。自然也就调用了
调用到了 vm._render() 方法,要返回一个VNode,调试发现 vm.$options.render 其实就是
Vue.prototype._render = function () {
// ...
var vm = this;
var ref = vm.$options;
var render = ref.render;
vnode = render.call(vm._renderProxy, vm.$createElement);
// ...
return vnode
}
// 而render方法其实就是用于输出一个虚拟节点
(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"app"}},[(message + 1 > 1)?_c('div',[_v(_s(message + 1))]):_e(),_v(" "),_c('button',{on:{"click":function($event){message += 1}}},[_v("阿道夫")])])}
})
然后结果交给 vm._update
Vue.prototype._update = function(vnode, hydrating) {
var vm = this;
var prevEl = vm.$el;
var prevVnode = vm._vnode;
// ...
vm._vnode = vnode;
// ...
vm.$el = vm.__patch__(prevVnode, vnode);
// ...
};
结论是 mount 阶段 初始化 Watcher,然后在 wathcer初始化后调用 get,get里 pushTarget(this),并且执行自身的getter也就是表达式,表达式的内容就是 vm._update(vm._render(), hydrating)
故而就开始执行 render函数,render 函数就是就是输出虚拟节点的。
以上是关于vue依赖收集代码分析的主要内容,如果未能解决你的问题,请参考以下文章