Skip to content

源码笔记(十):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

js
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 源码

js
  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 的内部实现及如何递归处理内部组件的卸载。