Vue2.x(含组件)主流程源码笔记(十):destroyed 阶段

上两章分析了 Vueupdate 阶段,本章我们以点击 点击让第二个App组件卸载 触发 hide 执行 this.isShow = false 为例,分析 Vuedestroyed 阶段。

前面逻辑同 update 阶段一致,触发该属性的订阅即 渲染 watcher 执行 run 方法,在 updateComponent 里先执行 vm._render 得到最新 vnode,然后执行 vm._update 更新 dom

updateChildren 循环里遍历比较子 vnode,可以看出只有最后一个 vnode 不同:旧 vnodeApp 组件 vnode,新 vnode 为注释 vnode。 则先调用 createElm 根据注释 vnode 创建真实 dom 并插入到对应位置;然后在 while 下次循环时,对旧的 App 组件 vnode 执行 removeVnodes 移除。

removeVnodes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function removeVnodes(vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
var ch = vnodes[startIdx];

if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch);
invokeDestroyHook(ch);
} else {
// Text node
removeNode(ch.elm);
}
}
}
}

removeVnodes 方法里根据 vnodes 的类型执行不同的方法:如果是标签节点,需要处理属性及钩子;如果是文本节点,则直接移除 dom 即可。

removeAndInvokeRemoveHook

removeAndInvokeRemoveHook 方法主要递归处理了子节点的属性并删除 dom 节点。

invokeDestroyHook

invokeDestroyHook 里先触发该组件 vnodedestroy 钩子,下文分析。然后执行 cbs.destroy 里的方法,包括 destroyunbindDirectives,然后如果有 vnode.children 即子节点,则对每项递归执行 invokeDestroyHook,到此,Vuedestroyed 阶段结束。

componentVNodeHooks.destroy

钩子里对组件 vnode 的组件实例进行判断,如果是被 keppAlive 组件包裹,则执行 deactivateChildComponent 修改状态并触发 deactivated 钩子;如果没有则执行组件实例的 $destroy 方法。

Vue.prototype.$destroy

Vue.prototype.$destroy 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
  Vue.prototype.$destroy = function () {
var vm = this;

if (vm._isBeingDestroyed) {
return;
}

callHook(vm, 'beforeDestroy');
vm._isBeingDestroyed = true; // remove self from parent

var parent = vm.$parent;

if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
} // teardown watchers


if (vm._watcher) {
vm._watcher.teardown();
}

var i = vm._watchers.length;

while (i--) {
vm._watchers[i].teardown();
} // remove reference from data ob
// frozen object may not have observer.


if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
} // call the last hook...


vm._isDestroyed = true; // invoke destroy hooks on current rendered tree

vm.__patch__(vm._vnode, null); // fire destroyed hook


callHook(vm, 'destroyed'); // turn off all instance listeners.

vm.$off(); // remove __vue__ reference

if (vm.$el) {
vm.$el.__vue__ = null;
} // release circular reference (#6759)


if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
}

Vue.prototype.$destroy 逻辑

  1. 触发 beforeDestroy 钩子
  2. 移除 Vue 组件链里的本组件实例引用
  3. 通知各订阅中心移除各订阅列表里该 watcher 的订阅
  4. 移除 data 对象的 __ob__
  5. 执行 vm.__patch__(vm._vnode, null) 即开始销毁组件实例,即对组件实例执行 invokeDestroyHook
  6. 触发 destroyed 钩子
  7. 移除组件实例上的事件
  8. 移除组件实例里的 $el 的实例引用
  9. 移除组件节点的 parent 引用

整体生命周期

vue beforeUpdate -> App two beforeDestroy -> Child beforeDestroy -> Child destroyed -> App two destroyed -> vue updated

本章小结

  1. 本章主要介绍当组件卸载时的 destroyed 阶段。
  2. 在更新 vnode 时,通过 removeVnodes 移除该组件,内部执行 removeAndInvokeRemoveHook(移除 dom),invokeDestroyHook$destroy) 两个方法。
  3. 介绍了 Vue.prototype.$destroy 的内部实现及如何递归处理内部组件的卸载。
---- 本文结束,感谢您的阅读 ----