Пожалуйста, объясните алгоритм сравнения vue

Vue.js
Пожалуйста, объясните алгоритм сравнения vue

diffчто это такое?diffЭто сравнение двух деревьев, рендеринг сгенерирует два дерева, новое дерево newVnode, старое дерево oldVnode, а затем сравнит и обновит два дерева, чтобы найти разницу.diff, полное имяdifference, в Vue алгоритм сравнения выполняется с помощью функции patch, поэтому его иногда называютpatch算法

⏳ Когда происходит различие

diffКогда это случилось? Конечно, мы можем сказать, что разница возникает при обновлении данных, потому что обновление данных запустит функцию рендеринга, чтобы получить дерево виртуального дома, и, наконец, страница будет повторно отображена.

Когда компонент создается, функция запускается, когда свойства или данные, от которых зависит компонент, изменяются (тот, что в коде ниже).updateComponent), эта функция делает две вещи:

  • бегать_renderСоздайте новое дерево виртуального дома (дерево vnode)
  • бегать_updata, передать корневой узел дерева виртуального дома, сгенерированного _render, сравнить старое и новое деревья и, наконец, завершить обновление реального дома.

Код ядра выглядит следующим образом, он отличается от исходного кода, но похож на него, это означает следующее:

// vue构造函数
function Vue(){
  // ... 其他代码
  var updateComponent = () => {
    this._update(this._render());
  }
  new Watcher(updateComponent);
  // ... 其他代码
}

diffпроизошло в_updateпока функция работает

вызовите сначала в коде_renderФункция получает корневой узел виртуального дома, а затем передает_updateфункция, вupdateComponentвходящийWatcherВ watcher можно следить за процессом выполнения функции, следить за тем, какие ответные данные используются во время выполнения функции и собирать зависимости.Для вотчера можно посмотреть мою последнюю статью:Эта статья поможет вам понять принцип отзывчивости vue2.

🔨 Что делает функция _update?

_updateФункция получитvondeпараметры, этоновыйСгенерированное дерево виртуальных домов, при этом функция _update передает текущий компонент_vnode属性, получатьСтарыйДерево виртуального дома. Функция _update сначала переназначает свойство _vnode компонента, чтобы оно указывало на новое дерево.

Просто выражается в коде:

function update(vnode){
    //vnode新树
    //this._vnode旧树
    this._vnode = vnode
}

Если вы рассматриваете только обновление дерева виртуального дома, этот шаг выполнен, но конечной целью является обновление страницы, поэтому вам нужно использовать diff для сравнения узлов дерева, чтобы вы могли сохранить старое дерево oldVnode для сравнения.

Просто выражается в коде:

 <body>
    <div id="app"></div>
    <script src="./vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
      });


      function update(vnode) {
        //vnode新树
        //this._vnode旧树
        
        let oldVnode = vm._vnode; //保存旧树
        
        this._vnode = vnode; //更新新树
      }
    </script>
  </body>

В сравненииoldVnodeа такжеvnodeВот и все,Цель сравнения - обновить реальный дом

image.png

Далее это будет судить, существует ли старое дерево Oldvnode:

  • Не существует: это означает, что это первая загрузка компонента, поэтому с помощью внутренней функции исправления новое дерево просматривается напрямую, для каждого узла создается настоящий DOM, а затем монтируется в каждый узел.elmатрибут

image.png

Просто выражается в коде:

<body>
    <div id="app"></div>
    <script src="./vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
      });
      console.log(vm);

      function update(vnode) {
        //vnode新树
        //this._vnode旧树
        let oldVnode = vm._vnode; //保存旧树
        this._vnode = vnode; //更新新树

        //如果旧树oldVnode不存在
        if(!oldVnode){
            this.__patch__(vm.$el,vnode);
        }
      }
    </script>
  </body>
  • Существование: это означает, что компонент был визуализирован ранее, поэтому внутренняя функция исправления используется для сравнения старого и нового деревьев для достижения следующих двух целей:
  1. Полная минимизация всех реальных домов
  2. Пусть узлы нового дерева соответствуют соответствующему реальному дому

image.png

🙌 Процесс сравнения функции патча

Объяснение терминологии:Как только вы увидите следующие слова, все они означают следующее

1."такой же": означает, что тип метки и значение ключа двух виртуальных узлов одинаковы, но элемент ввода также зависит от атрибута типа. Этот термин называется в исходном коде vuesameVnode, которая является функцией для определения того, являются ли два виртуальных узла одним и тем же узлом.

image.png

Пример: два виртуальных узла div одинаковы

<div>法医</div>
<div>前端猎手</div>

Типы этикетокdiv, значение ключа можно использовать не только в v-for traversal, но и в любой метке.В приведенных выше двух div значения ключа нет, поэтому обаundefined, поэтому тип метки и значение ключа одинаковы, независимо от того, совпадает ли содержимое, это другой узел:文本节点

<div key="fayi">法医</div>
<div key="qdls">前端猎手</div>

Вышеупомянутые два виртуальных узла отличаются, потому что значения ключей различны.

 <input type="text">
 <input type="radio">

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

2.«Новый элемент»: относится к созданию реального элемента dom на основе информации, предоставленной виртуальным узлом, и одновременному подключению его к атрибуту elm виртуального узла.

3.«Уничтожить элемент»: относится к: vnode.elm.remove()

4."обновить": относится к сравнительному обновлению двух виртуальных узлов, которое происходит только тогда, когда два виртуальных узла «одинаковы». Конкретный процесс будет описан позже.

5."Сравнение дочерних узлов": относится к сравнению дочерних узлов двух виртуальных узлов, конкретный процесс будет описан позже.

Подробный процесс

сравнение корневых узлов

image.png

Функция patch сначала сравнивает корневой узел

Если два узла:

  • "такой же",Войти"обновить"обработать
  1. Назначьте реальный дом старого узла на новый узел:newVnode.elm = oldVnode.elem, старый узел будет переработан механизмом сборки мусора
  2. Сравните атрибуты нового узла и старого узла и обновите изменения до реального DOM.
  3. Текущие два узла, новый и старый, обрабатываются и запускаются."Сравнение дочерних узлов"
  • Не делайте"такой же"
  1. новый узелрекурсия,«Новый элемент»
  2. старый узел«Уничтожить элемент»

Контрастный дочерний узел

Виртуальное дерево DOM завершено, и осталось только изменить реальный DOM, но эффективность изменения реального DOM требует больше времени.Отправная точка всего, для чего Vue:

  • попробуй ничего не делать

  • Если нет, попробуйте изменить только атрибуты элемента

  • Если это не сработает, попробуйте переместить элементы вместо их удаления и создания.

  • Если это не работает, удалите и создайте элементы

Процесс сравнения:

未命名绘图.png

проиллюстрировать:

  • Желтый круг: указывает на тот же тип узла, соответствующий старому дочернему узлу и новому дочернему узлу.
  • Число: указывает значение ключа, используемое для определения того, является ли это одним и тем же узлом.
  • Синий квадрат: указывает реальный дом, соответствующий старому дочернему узлу перед сравнением.
  • Стрелки: представляют указатель головы и указатель хвоста соответственно.

Далее, что нам нужно сделать, это сравнить旧子节点а также新子节点между差异, цель состоит в том, чтобы изменить真实dom, а новый виртуальный дочерний узел соответствует реальному dom, vue использует два указателя, чтобы указать на начало и конец дерева нового и старого дочерних узлов соответственно.

шаг:

  1. Сначала сравните указатели головок нового дерева и старого дерева и посмотрите, совпадают ли два узла. Из рисунка видно, что они одинаковы. Если они одинаковы, введите"обновить"Процесс: сначала назначьте реальный DOM старого узла новому узлу (подключите реальный DOM к новому дочернему узлу), затем сравните свойства старого и нового узлов в цикле, чтобы увидеть, есть ли какие-либо различия, и обновите измененные в реальный DOM и, наконец, принять сначала глубину (узел дерева идет до конца, а затем другой узел) рекурсивно зацикливается, есть ли у двух старых и новых дочерних узлов дочерние узлы, Если они существуют, то же самое Здесь мы предполагаем, что у него нет дочерних узлов.Серый означает, что он был обработан, Затем переместите две точки головы

未命名绘图.png

  1. Затем продолжайте сравнивать два указателя головы, чтобы увидеть, одинаковы ли два узла.Очевидно, что два узла отличаются, потому что значения ключей разные.Когда они разные, он не будет уничтожен, удален и установлен ., успокойся! Как было сказано ранее, старайтесь не управлять домом, он обязательно найдет тот же самый узел, одна дорога идет в черный, а затем он сравнит хвостовой указатель, вы можете видеть, что хвостовой указатель такой же, такой же, как и первый шаг:Операция свирепа, как тигр, сначала назначьте реальный дом старого узла новому узлу (подключите реальный дом к новому дочернему узлу), затем циклически сравните атрибуты старого и нового узлов, обновите измененные до реального дома, а затем рекурсивно зациклить два старых и новых узла Если дочерний узел имеет дочерние узлы, последние два хвостовых указателя перемещаются вперед

未命名绘图.png

  1. Затем продолжайте сравнивать головной указатель, он явно другой, а хвостовой указатель? Это не то же самое, потому что значение ключа все еще отличается. Затем он сравнит указатель головы и указатель хвоста, чтобы увидеть, совпадают ли они.Вы можете видеть, что указатель головы круга 2 старого узла и указатель круга 2 хвоста нового узла одинаковы, поэтому операция то же самое, что и предыдущие два шага, сноваОперация свирепа, как тигр, результат следующий:

未命名绘图.png

Здесь следует обратить внимание на то, что реальный дом должен находиться во взаимно однозначном соответствии с новым виртуальным дочерним узлом, поэтому в дополнение к обновлению измененного места,位置移动, Переместитесь к задней части указателя хвоста старого дерева, последний указатель старого дерева переместится назад, указатель нового дерева переместится вперед, как показано ниже:

未命名绘图.png

  1. Продолжайте сравнивать, новый и старый указатели головы разные, указатели хвоста разные, и две головы и хвосты также разные, и тогда он будет использовать новый указатель головы дерева в качестве эталона, зацикливать старый виртуальный дочерний узел, и посмотрите, существует ли новый круг дерева 3 в старом виртуальном дочернем узле, если он существует, то где он? После того, как он будет найден, он будет повторно использован, подключен и обновлен до реального DOM, если есть изменение. такой же, как и предыдущие шаги, и настоящий DOM также должен быть выполнен.位置移动, переместиться перед старым указателем головы дерева. Затем указатель новой вершины дерева продолжает двигаться назад к позиции окружности 9, как показано ниже:

未命名绘图.png

  1. Продолжайте сравнивать, новый и старый указатели головы разные, и указатели хвоста разные, но новый указатель головы дерева и старый указатель хвоста дерева одинаковы, и операция такая же, как и в предыдущих шагах, но все еще необходимо переместить позицию перед старым указателем головы дерева. Затем новый указатель начала дерева перемещается назад, что совпадает с новым указателем хвоста дерева, а старый указатель хвоста дерева перемещается вперед в положение круга 1, как показано на следующем рисунке:

未命名绘图.png

  1. Продолжайте сравнивать, новый и старый два указателя головы дерева отличаются, указатель хвоста отличается, и два указателя головы и хвоста также различны, а затем он использует новый указатель головы дерева в качестве эталона, зацикливает старый виртуальный дочерний узел , и находит, существует ли в старом дереве круг 8. Это видно из«Новый элемент». Затем указатель новой вершины дерева продолжает двигаться назад к позиции окружности 2, как показано на рисунке:

未命名绘图.png

  1. Когда указатель головы перемещается в положение круга 2, указатель головы больше недействителен. Когда указатель головы превышает указатель хвоста, цикл заканчивается. Из процесса мы можем видеть, что новое дерево завершает цикл первым, но в старом дереве все еще есть оставшиеся узлы, что означает, что оставшиеся узлы в старом дереве — это все узлы, которые следует удалить, и соответствующий реальный дом также будет удален.

未命名绘图.png

Наконец, настоящий DOM сгенерирован, и мы создаем только один новый элемент во всем процессе, как показано на следующем рисунке:

未命名绘图.png

Во время интервью вам также будут заданы вопросы об алгоритме diff. Ниже приведены справочные ответы:

当组件创建和更新时,vue会执行内部的update函数,该函数使用render函数生成的虚拟dom树,将新旧两树进行对比,找到差异点,最终更新到真实dom

对比差异的过程叫diff,vue在内部通过一个叫patch的函数完成该过程

在对比时,vue采用深度优先、同级比较的方式进行比对。同级比较就是说它不会跨越结构进行比较

在判断两个节点是否相同时,vue是通过虚拟节点的key和tag来进行判断的

具体来说,首先对根节点进行对比,如果相同则将旧节点关联的真实dom的引用挂到新节点上,然后根据需要更新属性到真实dom,然后再对比其子节点数组;如果不相同,则按照新节点的信息递归创建所有真实dom,同时挂到对应虚拟节点上,然后移除掉旧的dom。

在对比其子节点数组时,vue对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,这样做的目的是尽量复用真实dom,尽量少的销毁和创建真实dom。如果发现相同,则进入和根节点一样的对比流程,如果发现不同,则移动真实dom到合适的位置。

这样一直递归的遍历下去,直到整棵树完成对比。

😊 Ну, вышесказанное - это моя публикация.diff算法Если у вас есть другое понимание, вы можете обсудить уток в области комментариев~

Надеюсь, вам понравится 👍 и вы поддержите его~ 😘, у меня будет больше мотивации 🤞