- 使用 GitHub 阅览
对于 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 此时会自动调用 beforeDestroydestroyed 方法
一旦组件被销毁, 它将不再响应式, 即更改 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 出来的
- 轻松掌握 - vue - 实例的生命周期 /
-Vue2.0 探索之路生命周期和钩子函数的一些理解
来源: https://juejin.im/post/5aacfdfa51882555850753b5