Хитрый интервьюер: Что сделал узел vue, когда он был уничтожен?

Vue.js
Хитрый интервьюер: Что сделал узел vue, когда он был уничтожен?

Споткнулся по дороге на собеседование
Но на этот раз я не ожидал встретить волка.

«Вы знаете, что делает узел vue, когда он уничтожается?»
"..."

жизненный цикл

Мы знаем, что жизненный цикл vue имеет эти

var LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
];

Где «beforeDestroy», «destroyed» — это функции жизненного цикла, которые мы можем написать сами
Но кроме того, на самом деле, у vue есть некоторые хуки-функции, которые используются внутри, в том числе хуки уничтожения.

var hooks = ['create', 'activate', 'update', 'remove', 'destroy'];

Итак, когда узел уничтожается, что он делает?Во-первых, давайте рассмотрим, как запускается узел в Vue для уничтожения.

Как узел запускает разрушение?

Когда мы изменяем данные
Обновление всех связанных наблюдателей в хранилищах данных инициируется захватным набором.
Затем запустите обновление компонента, чтобы выполнить исправление старых и новых узлов для выполнения соответствующего обновления dom.

Конечно, реализация функции патча очень сложна, сомневающиеся студенты могут поискать информацию.
Сегодня нам просто нужно знать, что в функции исправления есть три места, которые вызовут разрушение компонента.

1. Удаление или замена узла

Это легко понять, например, мы используем v-if, чтобы контролировать, сохраняется компонент или нет.
Старого узла больше не существует, конечно, его нужно уничтожить.

Или компоненты больше не являются одним и тем же компонентом, наиболее распространенными примерами являются , и т. д.
Эти компоненты будут динамически менять свои типы компонентов, будут создаваться новые компоненты, поэтому старые необходимо уничтожить.

2. Удалить детей

Затем patch переходит на второй этап, patchVNode, где он сравнивает, есть ли у старого и нового узлов дочерние узлы.
Если у нового узла нет потомков, значит узел удален, нужно уничтожить потомков старого узла

3. Дочерние узлы в дочерних элементах удаляются или заменяются

Наконец, войдите в стадию updateChildren, которую мы часто называем стадией сравнения.
Этап diff будет выполнять цикл от начала и конца старого и нового дочерних узлов.
Соответственно введите первую позицию, позицию хвоста, голову и хвост, и хвост хвоста, чтобы судить
Делай патчи, добавляй, двигай влево-вправо
Когда цикл завершен, если новый дочерний узел был зациклен, старый дочерний узел не был зациклен.
Указывает, что избыточные узлы необходимо удалить.

Так что же делает метод destroy? Давайте посмотрим на исходный код

функция removeVnode

Вы можете видеть, что описанная выше операция удаления — это метод, называемый

function removeVnodes (parentElm, 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);
        }
      }
    }
  }

Завести сына очень просто, на самом деле, это делает две вещи
Выполните метод удаления узла платформы, затем выполните хук удаления и уничтожения.

function removeAndInvokeRemoveHook (vnode, rm) {
    if (isDef(rm) || isDef(vnode.data)) {
      var i;
      var listeners = cbs.remove.length + 1;
      if (isDef(rm)) {
        // we have a recursively passed down rm callback
        // increase the listeners count
        rm.listeners += listeners;
      } else {
        // directly removing
        rm = createRmCb(vnode.elm, listeners);
      }
      // recursively invoke hooks on child component root node
      if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
        removeAndInvokeRemoveHook(i, rm);
      }
      for (i = 0; i < cbs.remove.length; ++i) {
        cbs.remove[i](vnode, rm);
      }
      if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
        i(vnode, rm);
      } else {
        rm();
      }
    } else {
      removeNode(vnode.elm);
    }
  }
function invokeDestroyHook (vnode) {
    var i, j;
    var data = vnode.data;
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); }
      for (i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](vnode); }
    }
    if (isDef(i = vnode.children)) {
      for (j = 0; j < vnode.children.length; ++j) {
        invokeDestroyHook(vnode.children[j]);
      }
    }
  }

Кроме того, можно обнаружить, что на самом деле эти два метода очень похожи.
Все они являются рекурсивными вызовами самих себя, а их дочерние узлы удаляют хуки и уничтожают хуки.
Среди них удаление немного особенное.После выполнения функции ловушки также будет выполнен метод узла удаления реальной платформы.

removeNode(childElm);

удалить и уничтожить крючок

Итак, теперь все в порядке, если вы знаете, что выполняется в функции ловушки.
Мы находим, где определены все функции ловушек, находим все функции ловушек

var baseModules = [
  ref,
  directives
]

var platformModules = [
  attrs,
  klass,
  events,
  domProps,
  style,
  transition
]

Это базовый модуль и модуль платформы, определенные vue.
Но не всем модулям нужно выполнять функции-ловушки на этапе удаления и на этапе уничтожения.

Заглянув дальше в код, мы видим, что только
ref,directives,transitionХук удаления или уничтожения определен

ref

Где ref — обновить справочную информацию, которую мы определили. Хук уничтожения должен очистить ссылку на реальный дом, код выглядит следующим образом

var ref = {
  create: function create (_, vnode) {
    ...
  },
  update: function update (oldVnode, vnode) {
    ...
  },
  destroy: function destroy (vnode) {
    registerRef(vnode, true);
  }
}

function registerRef (vnode, isRemoval) {
  ...
  if (isRemoval) {
    if (Array.isArray(refs[key])) {
      remove(refs[key], ref);
    } else if (refs[key] === ref) {
      refs[key] = undefined;
    }
  } else {
    ...
  }
}

directives

директивы должны обновить нашу пользовательскую директиву, чтобы вызвать событие отмены привязки директивы


function updateDirectives (oldVnode, vnode) {
  if (oldVnode.data.directives || vnode.data.directives) {
    _update(oldVnode, vnode);
  }
}

function _update (oldVnode, vnode) {
  var isCreate = oldVnode === emptyNode;
  ...
  if (!isCreate) {
    for (key in oldDirs) {
      if (!newDirs[key]) {
        // no longer present, unbind
        callHook$1(oldDirs[key], 'unbind', oldVnode, oldVnode, isDestroy);
      }
    }
  }
}

transition

Давайте снова посмотрим на переход, у него есть только хук удаления фактически выполнить

var transition = inBrowser ? {
  create: _enter,
  activate: _enter,
  remove: function remove$$1 (vnode, rm) {
    /* istanbul ignore else */
    if (vnode.data.show !== true) {
      leave(vnode, rm);
    } else {
      rm();
    }
  }
} : {}

Здесь вы можете видеть, что при его уничтожении сработает метод перехода leave
Который выполнит соответствующую анимацию выхода

Метод leave также выполняется в методе update инструкции show, что объясняет, почему v-show также может запускать анимацию перехода.

уничтожить хук для компонентов vue

Помимо хуков модулей, конечно же очень важны хуки самих компонентов vue.
Когда createComponent создает компонент, installComponentHooks(data);
Здесь функция ловушки будет привязана к data.hook

Затем, когда узел будет уничтожен, если есть этот хук, он будет выполнен

 if (isDef(i = data.hook) && isDef(i = i.destroy)) { i(vnode); }

Тогда нам просто нужно посмотреть, что происходит в уничтожении

var componentVNodeHooks = {
  init: function init (
    vnode,
    hydrating,
    parentElm,
    refElm
  ) {
    ...
  },

  prepatch: function prepatch (oldVnode, vnode) {
    ...
  },

  insert: function insert (vnode) {
    ...
  },

  destroy: function destroy (vnode) {
    var componentInstance = vnode.componentInstance;
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        componentInstance.$destroy();
      } else {
        deactivateChildComponent(componentInstance, true /* direct */);
      }
    }
  }
};

Как вы можете видеть здесь, когда компонент уничтожается
заключается в выполнении функции компонента $destroy

Конечно, если компонент закэширован keepAlive, он не будет уничтожен, а только войдет в деактивный процесс.

который уничтожает эти операции

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;
    }
  };
}

Здесь сначала запустите функцию жизненного цикла beforeDestroy, которую мы написали сами.
Затем очистите дочерние узлы vnode, а затем очистите все соответствующие зависимости наблюдателей.
Следующий абзац интересный

vm.__patch__(vm._vnode, null);
function patch (oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {
    if (isUndef(vnode)) {
      if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
      return
    }
    ...
}

На самом деле это рекурсивный вызов для уничтожения компонента, а затем запуска функции уничтоженного жизненного цикла.
Итак, порядок выполнения времени уничтожения наших компонентов таков:

Родительский компонент beforeDestroy -> дочерний компонент beforeDestroy -> уничтожение дочернего компонента -> уничтожение родительского компонента

Затем спуститесь вниз и выполните функцию $off
пустые _events

Наконец, удаление ссылок на соответствующий узел закончено

Суммировать

Итак, подводя итог, Vue по-прежнему выполняет множество операций по уничтожению узла.
По очереди выполнит хук удаления или уничтожения перехода ref Director
Затем выполните метод уничтожения компонента vue.

over~