对于Vue的实例,比如
const app = new Vue({...})
复制代码
浏览器解析到这段代码的时候,自动执行beforeCreate => created => beforeMount => mounted方法,每当data的某个属性值更改了,比如==app.mes = "hi"==,自动执行beforeUpdate => updated方法。
beforeCreate:
el : undefined
data : undefined
message: undefined
复制代码
created:
el : undefined
data : [object Object]
message: hi
复制代码
beforeMount:
el : [object HTMLDivElement]
<div id="app"><p>{{ message }}</p></div>
data : [object Object]
message: hi
复制代码
mounted:
el : [object HTMLDivElement]
<div id="app"><p>hi</p></div>
data : [object Object]
message: hi
复制代码
当需要销毁这个实例的时候,需要手动执行app.$destroy()。然后Vue此时会自动调用beforeDestroy、destroyed方法。
一旦组件被销毁,它将不再响应式,即更改data的属性值,比如app.mes = "hello",页面不会同时被更新,页面上的数据任然显示的是 hi
此时需要手动调用app.$mount(),这个方法在这个时候被手动调用的时候,Vue会自动按照下面的顺序执行以下方法: beforeMount => beforeUpdate => updated => mounted。
此时,我们会发现页面变成了hello 我的理解是,虽然之前app被销毁了,但是对于mes属性的dep里面,通过监控它的watcher,仍然是变成了“hello”,但是不会调用底层的compile(this.cb)来渲染视图。而当手动调用app.$mount()的时候,它的compile方法被激活,又变成响应式的了。 当我们调用destroy方法的时候,其实调用了app._watcher的teardown方法,下面代码所示(在执行这个destroy之前,我们调用了callHook这个钩子函数):
Vue.prototype.$destroy = function () {
var vm = this;
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy');
if (vm._watcher) {
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
}
复制代码
_watcher的active值被置为false,然后调用removeSub将该watcher所依赖的订阅器全都退订(从每个dep的subs数组中移除)。
Watcher.prototype.teardown = function teardown () {
var this$1 = this;
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this);
}
var i = this.deps.length;
while (i--) {
this$1.deps[i].removeSub(this$1);
}
this.active = false;
}
};
复制代码
看到这里,我们知道,尽管被destroy,_watcher还是那个_watcher(此时,如果调用$mount,就不是了)。什么意思呢?就是说每个属性值还是被放在它们各自的watcher中被监控着的。只不过,由于这些watcher都从deps里面被退订了,那么一旦它们的值被更改,将不会调用watcher的update(因为每个watcher的update方法是在dep的notify里面被调用的)。
我们看到update被调用的时候,实际是调用了run方法,也就是说真正的update(this.cb)实在run里面被调用。
在run里面,先判断active,如果是false就不更新视图(不再响应式了)
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
//
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
/..../
{this.cb.call(this.vm, value, oldValue);
}
}
}
};
复制代码
好了,当我们再次调用app.$mount(),其实我们此时是重新编译了一遍节点。此时经历了这么个过程: beforeMount => beforeUpdate => updated => mounted
查看源码,我们发现$mount方法里面调用了compileToFunctions方法,而compileToFunctions方法里面调用了compile方法,也就是说,此时我们为app.mes属性,重新new了一个Watcher。
var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el,
hydrating){
//.......
var ref = compileToFunctions(template, {
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
//....
}
//...
function compileToFunctions(){
//....
var compiled = compile(template, options);
//...
}
复制代码
如果读者对以上分析感到疑惑,我们可以测试一下:
在调用destroy之前,我们先声明一个变量p来保持对此时的watcher对象的引用(由于jvm是根据对对象的持有,来决定是否从内存中删除此对象。所以这个旧的watcher不会被删除)
// teardown之前
p=app._watcher.deps[0].subs[0];
// teardown之后,又调用$mount()
pp = app._watcher.deps[0].subs[0];
// 比较
p === pp //输出false,说明这个watcher是新new出来的
复制代码