源码笔记(十):destroyed 阶段
上两章分析了 Vue 的 update 阶段,本章我们以点击 点击让第二个App组件卸载 触发 hide 执行 this.isShow = false 为例,分析 Vue 的 destroyed 阶段。
前面逻辑同 update 阶段一致,触发该属性的订阅即 渲染 watcher 执行 run 方法,在 updateComponent 里先执行 vm._render 得到最新 vnode,然后执行 vm._update 更新 dom。
在 updateChildren 循环里遍历比较子 vnode,可以看出只有最后一个 vnode 不同:旧 vnode 为 App 组件 vnode,新 vnode 为注释 vnode。 则先调用 createElm 根据注释 vnode 创建真实 dom 并插入到对应位置;然后在 while 下次循环时,对旧的 App 组件 vnode 执行 removeVnodes 移除。
removeVnodes
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 里先触发该组件 vnode 的 destroy 钩子,下文分析。然后执行 cbs.destroy 里的方法,包括 destroy 和 unbindDirectives,然后如果有 vnode.children 即子节点,则对每项递归执行 invokeDestroyHook,到此,Vue 的 destroyed 阶段结束。
componentVNodeHooks.destroy
钩子里对组件 vnode 的组件实例进行判断,如果是被 keppAlive 组件包裹,则执行 deactivateChildComponent 修改状态并触发 deactivated 钩子;如果没有则执行组件实例的 $destroy 方法。
Vue.prototype.$destroy
Vue.prototype.$destroy 源码
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 逻辑
- 触发
beforeDestroy钩子 - 移除
Vue组件链里的本组件实例引用 - 通知各订阅中心移除各订阅列表里该
watcher的订阅 - 移除
data对象的__ob__ - 执行
vm.__patch__(vm._vnode, null)即开始销毁组件实例,即对组件实例执行invokeDestroyHook - 触发
destroyed钩子 - 移除组件实例上的事件
- 移除组件实例里的
$el的实例引用 - 移除组件节点的
parent引用
整体生命周期
vue beforeUpdate -> App two beforeDestroy -> Child beforeDestroy -> Child destroyed -> App two destroyed -> vue updated
本章小结
- 本章主要介绍当组件卸载时的
destroyed阶段。 - 在更新
vnode时,通过removeVnodes移除该组件,内部执行removeAndInvokeRemoveHook(移除dom),invokeDestroyHook($destroy) 两个方法。 - 介绍了
Vue.prototype.$destroy的内部实现及如何递归处理内部组件的卸载。