源码笔记(十):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
的内部实现及如何递归处理内部组件的卸载。