предисловие
Интервьюер: «Вы понимаете虚拟DOM(Virtual DOM)
а такжеDiff算法
, пожалуйста, опишите их";
Я: "Эээ...гусь, это", кончено😰, вдруг мой IQ не в сети, я плохо организовала язык, плохо ответила, или вообще не смогла ответить;
Итак, на этот раз я обобщу соответствующие моменты знаний, чтобы у вас было четкое понимание, а также чтобы вы могли столкнуться с этой ситуацией в будущем.Спокойствие и хладнокровие, легко справляется:
Связанные точки знаний:
- Виртуальный DOM (виртуальный DOM):
-
что такое виртуальный дом
-
Зачем использовать виртуальный дом
-
Виртуальная DOM-библиотека
-
- РАЗЛИЧНЫЙ алгоритм:
- Источник snabDom
- функция инициализации
- h функция
- патч функция
- функция patchVnode
- функция updateChildren
- Источник snabDom
Виртуальный DOM (Виртуальный DOM)
Что такое виртуальный DOM
В одном предложении виртуальный DOM — это описание реального DOM.JavaScript-объект, этого изображения может быть недостаточно, тогда давайте приведем пример 🌰: используйте код для описания соответственно真实DOM
так же как虚拟DOM
真实DOM
:
<ul class="list">
<li>a</li>
<li>b</li>
<li>c</li>
</ul>
对应的虚拟DOM
:
let vnode = h('ul.list', [
h('li','a'),
h('li','b'),
h('li','c'),
])
console.log(vnode)
консольная печатьVnode:
Исходный код объекта JS (Vnode) виртуального DOM, сгенерированного функцией h:
export interface VNodeData {
props?: Props
attrs?: Attrs
class?: Classes
style?: VNodeStyle
dataset?: Dataset
on?: On
hero?: Hero
attachData?: AttachData
hook?: Hooks
key?: Key
ns?: string // for SVGs
fn?: () => VNode // for thunks
args?: any[] // for thunks
[key: string]: any // for any other 3rd party module
}
export type Key = string | number
const interface VNode = {
sel: string | undefined, // 选择器
data: VNodeData | undefined, // VNodeData上面定义的VNodeData
children: Array<VNode | string> | undefined, //子节点,与text互斥
text: string | undefined, // 标签中间的文本内容
elm: Node | undefined, // 转换而成的真实DOM
key: Key | undefined // 字符串或者数字
}
Пополнить:
Вышеупомянутая функция h может быть всем немного знакома, но я долго ее не запоминал, не беда, я помогу вам вспомнить;开发中常见的现实场景,render函数渲染
:
// 案例1 vue项目中的main.js的创建vue实例
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
//案例2 列表中使用render渲染
columns: [
{
title: "操作",
key: "action",
width: 150,
render: (h, params) => {
return h('div', [
h('Button', {
props: {
size: 'small'
},
style: {
marginRight: '5px',
marginBottom: '5px',
},
on: {
click: () => {
this.toEdit(params.row.uuid);
}
}
}, '编辑')
]);
}
}
]
Зачем использовать виртуальный DOM
- Фреймворк MVVM решает проблему синхронизации представления и состояния
- Механизм шаблонов может упростить работу с представлением, нет возможности отслеживать состояние
- Виртуальный DOM отслеживает изменения состояния
- Обратитесь к гитхабуvirtual-domописание мотивации
- Виртуальный DOM может поддерживать состояние программы и отслеживать последнее состояние.
- Обновите настоящий DOM, сравнив разницу между двумя состояниями до и после.
- Использование на разных платформах
- Рендеринг DOM платформы браузера
- Рендеринг SSR на стороне сервера (Nuxt.js/Next.js), внешний интерфейс — направление vue, последний — направление реакции
- Нативное приложение (Weex/React Native)
- Небольшие программы (mpvue/uni-app) и т.д.
- Существует много атрибутов реального DOM, и стоимость создания узлов DOM очень высока.
- Виртуальный DOM — это обычный объект JavaScript, для описания свойств не требуется много времени, а накладные расходы на создание невелики.
- Повышение производительности рендеринга в сложных ситуациях просмотра(Производительность операционной высока, и сокращение объема операционной может улучшить производительность)
душа просит: Будет ли использование виртуального DOM быстрее, чем прямой рендеринг реального DOM? Конечно, ответ такой:否定
Да, послушай меня:
Пример: DOMA->DOMB при изменении узла
Вышеупомянутая ситуация:示例1
заключается в созданииDOMB
затем заменитьDOMA
;
示例2
идти创建虚拟DOM+DIFF算法
Сравнение найденоDOMB
а такжеDOMA
Не тот же узел, и, наконец, создатьDOMB
затем заменитьDOMA
;
Хорошо видно, что быстрее 1. Для того же результата 2 нужно создать виртуальный DOM+DIFF для сравнения.
Таким образом, утверждение о том, что использование виртуального DOM быстрее, чем прямое управление реальным DOM,错误的,不严谨的
Пример: Когда содержимое дочернего узла в дереве DOM изменяется:
Когда некоторые сложные узлы, такие как родительский узел с несколькими дочерними узлами, когда изменилось только содержимое одного дочернего узла, то нам не нужно быть похожими на示例1
перерисовать этоDOM树
,В настоящее время虚拟DOM+DIFF算法
может быть хорошо представлен示例2
использовать虚拟DOM+Diff算法
Перейдите, чтобы найти измененный дочерний узел и обновить его содержимое.
Суммировать:Повышение производительности рендеринга в сложных ситуациях просмотра,потому что虚拟DOM+Diff算法
Может точно найти место, где меняется DOM-дерево, сократить операции с DOM (переставить и перерисовать)
библиотека виртуального дома
-
Snabbdom
- Виртуальный DOM, используемый внутри Vue.js2.x, представляет собой модифицированный Snabbdom.
- Около 200SLOC (одна строка кода)
- Расширяемый с помощью модулей
- Исходный код разработан с использованием TypeScript
- Один из самых быстрых виртуальных DOM
- virtual-dom
Алгоритм сравнения
После прочтения приведенной выше статьи я полагаю, что у всех есть предварительное представление об алгоритме Diff.Да, алгоритм Diff на самом деле предназначен для поиска разницы между ними;
Первая концепция алгоритма diff заключается в том, что объектом Diff является виртуальный DOM (виртуальный DOM), а обновление реального DOM является результатом алгоритма diff.
я разорву его на частиsnabbdom
Основная часть исходного кода открыта для всехDiff
Сердце, потерпи, не закрывай страницу, я знаю, вы все такие:
ядро снаббдома
-
init()
модуль настройки.создатьpatch()
функция - использовать
h()
Функция для создания объекта JavaScript(Vnode)
описывать真实DOM
-
patch()
Сравнивать新旧两个Vnode
- обновить изменения до
真实DOM树
функция инициализации
Модуль настраивается во время функции инициализации, а затем создается функция patch () Сначала мы используем случай сцены, чтобы иметь интуитивное отражение:
import {init} from 'snabbdom/build/package/init.js'
import {h} from 'snabbdom/build/package/h.js'
// 1.导入模块
import {styleModule} from "snabbdom/build/package/modules/style";
import {eventListenersModule} from "snabbdom/build/package/modules/eventListeners";
// 2.注册模块
const patch = init([
styleModule,
eventListenersModule
])
// 3.使用h()函数的第二个参数传入模块中使用的数据(对象)
let vnode = h('div', [
h('h1', {style: {backgroundColor: 'red'}}, 'Hello world'),
h('p', {on: {click: eventHandler}}, 'Hello P')
])
function eventHandler() {
alert('疼,别摸我')
}
const app = document.querySelector('#app')
patch(app,vnode)
Когда init использует импортированные модули, его можно создать в функции h с помощью API, предоставляемого этими модулями.虚拟DOM(Vnode)对象
; использовано выше样式模块
так же как事件模块
Пусть созданный виртуальный DOM имеет атрибуты стиля и атрибуты событий и, наконец, проходитpatch函数
В сравнении两个虚拟dom
(Сначала приложение будет преобразовано в виртуальный дом), и представление будет обновлено;
Давайте кратко рассмотрим часть исходного кода init:
// src/package/init.ts
/* 第一参数就是各个模块
第二参数就是DOMAPI,可以把DOM转换成别的平台的API,
也就是说支持跨平台使用,当不传的时候默认是htmlDOMApi,见下文
init是一个高阶函数,一个函数返回另外一个函数,可以缓存modules,与domApi两个参数,
那么以后直接只传oldValue跟newValue(vnode)就可以了*/
export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {
...
return function patch (oldVnode: VNode | Element, vnode: VNode): VNode {}
}
h функция
также используется в некоторых местахcreateElement
назвать их, это одно и то же, оба созданы虚拟DOM
Да, в приведенной выше статье я считаю, что все уже имеют предварительное представление о функции h и уже связали сценарий использования, поэтому я не буду вводить сценарный кейс, а перейду сразу к части исходного кода:
// h函数
export function h (sel: string): VNode
export function h (sel: string, data: VNodeData | null): VNode
export function h (sel: string, children: VNodeChildren): VNode
export function h (sel: string, data: VNodeData | null, children: VNodeChildren): VNode
export function h (sel: any, b?: any, c?: any): VNode {
var data: VNodeData = {}
var children: any
var text: any
var i: number
...
return vnode(sel, data, children, text, undefined) //最终返回一个vnode函数
};
// vnode函数
export function vnode (sel: string | undefined,
data: any | undefined,
children: Array<VNode | string> | undefined,
text: string | undefined,
elm: Element | Text | undefined): VNode {
const key = data === undefined ? undefined : data.key
return { sel, data, children, text, elm, key } //最终生成Vnode对象
}
Суммировать:h函数
сначала создайтеvnode
функция, тоvnode
функция для созданияVnode
объект (виртуальный объект DOM)
Пополнить:
В части исходного кода функции h函数重载
Концепция, кратко поясню:
- Функции с разным количеством или типами параметров ()
- В JavaScript нет концепции перегрузки
- В TypeScript есть перегрузки, но реализация перегрузки по-прежнему настраивает параметры через код
Концепция перегрузки связана с параметрами и не имеет ничего общего с возвращаемыми значениями.
- Пример 1 (перегрузка функции - количество параметров)
function add(a:number,b:number){
console.log(a+b)
}
function add(a:number,b:number,c:number){
console.log(a+b+c)
}
add(1,2)
add(1,2,3)
- Пример 2 (перегрузка функции - тип параметра)
function add(a:number,b:number){
console.log(a+b)
}
function add(a:number,b:string){
console.log(a+b)
}
add(1,2)
add(1,'2')
функция исправления (ядро)
Если вы читали предыдущее предзнаменование, вы можете отвлечься, увидев это,醒醒啊,这是核心啊,上高地了兄弟
;
- pactch(oldVnode,newVnode)
- Отобразите измененное содержимое в новом узле в реальном DOM и, наконец, верните новый узел в качестве старого узла (ядра) для следующей обработки.
- Сравните старое и новое
VNode
Это один и тот же узел (ключ и sel узла одинаковы) - Если это не тот же узел, удалите предыдущий контент и повторите рендеринг.
- Если это тот же узел, то судить о новом
VNode
Здесьtext
, если есть и иoldVnode
изtext
Различное непосредственное обновление текстового содержимого(patchVnode)
- Если у нового VNode есть дочерние элементы, определите, изменился ли дочерний узел.
(updateChildren,最麻烦,最难实现)
Исходный код:
return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {
let i: number, elm: Node, parent: Node
const insertedVnodeQueue: VNodeQueue = []
// cbs.pre就是所有模块的pre钩子函数集合
for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]()
// isVnode函数时判断oldVnode是否是一个虚拟DOM对象
if (!isVnode(oldVnode)) {
// 若不是即把Element转换成一个虚拟DOM对象
oldVnode = emptyNodeAt(oldVnode)
}
// sameVnode函数用于判断两个虚拟DOM是否是相同的,源码见补充1;
if (sameVnode(oldVnode, vnode)) {
// 相同则运行patchVnode对比两个节点,关于patchVnode后面会重点说明(核心)
patchVnode(oldVnode, vnode, insertedVnodeQueue)
} else {
elm = oldVnode.elm! // !是ts的一种写法代码oldVnode.elm肯定有值
// parentNode就是获取父元素
parent = api.parentNode(elm) as Node
// createElm是用于创建一个dom元素插入到vnode中(新的虚拟DOM)
createElm(vnode, insertedVnodeQueue)
if (parent !== null) {
// 把dom元素插入到父元素中,并且把旧的dom删除
api.insertBefore(parent, vnode.elm!, api.nextSibling(elm))// 把新创建的元素放在旧的dom后面
removeVnodes(parent, [oldVnode], 0, 0)
}
}
for (i = 0; i < insertedVnodeQueue.length; ++i) {
insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i])
}
for (i = 0; i < cbs.post.length; ++i) cbs.post[i]()
return vnode
}
Дополнение 1: функция sameVnode
function sameVnode(vnode1: VNode, vnode2: VNode): boolean { 通过key和sel选择器判断是否是相同节点
return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel
}
patchVnode
- триггер первой ступени
prepatch
функция иupdate
функция (оба запускают функцию предварительного исправления, два не совсем одинаковых запускают функцию обновления) - Второй этап, реальное сравнение старого и нового
vnode
где разница - Третий этап, триггер
postpatch
узел обновления функции
Исходный код:
function patchVnode(oldVnode: VNode, vnode: VNode, insertedVnodeQueue: VNodeQueue) {
const hook = vnode.data?.hook
hook?.prepatch?.(oldVnode, vnode)
const elm = vnode.elm = oldVnode.elm!
const oldCh = oldVnode.children as VNode[]
const ch = vnode.children as VNode[]
if (oldVnode === vnode) return
if (vnode.data !== undefined) {
for (let i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
vnode.data.hook?.update?.(oldVnode, vnode)
}
if (isUndef(vnode.text)) { // 新节点的text属性是undefined
if (isDef(oldCh) && isDef(ch)) { // 当新旧节点都存在子节点
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue) //并且他们的子节点不相同执行updateChildren函数,后续会重点说明(核心)
} else if (isDef(ch)) { // 只有新节点有子节点
// 当旧节点有text属性就会把''赋予给真实dom的text属性
if (isDef(oldVnode.text)) api.setTextContent(elm, '')
// 并且把新节点的所有子节点插入到真实dom中
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) { // 清除真实dom的所有子节点
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
} else if (isDef(oldVnode.text)) { // 把''赋予给真实dom的text属性
api.setTextContent(elm, '')
}
} else if (oldVnode.text !== vnode.text) { //若旧节点的text与新节点的text不相同
if (isDef(oldCh)) { // 若旧节点有子节点,就把所有的子节点删除
removeVnodes(elm, oldCh, 0, oldCh.length - 1)
}
api.setTextContent(elm, vnode.text!) // 把新节点的text赋予给真实dom
}
hook?.postpatch?.(oldVnode, vnode) // 更新视图
}
Это может немного сбивать с толку, вот еще одна карта разума:
Отступление: введение в алгоритм Diff
Традиционный алгоритм сравнения
- Алгоритм сравнения в виртуальном DOM
-
传统算法
Найдите разницу между каждым узлом двух деревьев - Он запустит n1 (количество узлов dom1) * n2 (количество узлов dom2), чтобы сравнить, найти разницу и затем обновить ее.
Оптимизация алгоритма сравнения snabdom
- Согласно характеристикам DOM, Snbbdom сделал традиционный алгоритм сравнения
优化
- Узлы редко манипулируются на разных уровнях во время манипулирования DOM.
- сравнить только
同级别
узел
Ниже мы представимupdateChildren
Как функция сравнивает дочерние узлы异同
,СлишкомDiff算法
Ядро и сложная точка внутри;
updateChildren (ядро в ядре: оценка разницы между дочерними узлами)
- Эта функция делится на три части,
部分1:声明变量
,部分2:同级别节点比较
,部分3:循环结束的收尾工作
(Смотри ниже);
-
同级别节点比较
из五种
условие:-
oldStartVnode/newStartVnode
(старый начальный узел/новый начальный узел) то же самое -
oldEndVnode/newEndVnode
(старый конечный узел/новый конечный узел) то же самое -
oldStartVnode/newEndVnode
(старый начальный узел/новый конечный узел) то же самое -
oldEndVnode/newStartVnode
(старый конечный узел/новый начальный узел) то же самое -
特殊情况当1,2,3,4的情况都不符合
будет выполнен, когдаoldVnodes
найти внутриnewStartVnode
тот же узел, а затем перешел кoldStartVnode
, если не найдено, тоoldStartVnode
Создавать
-
- Процесс выполнения представляет собой цикл.В каждом цикле, пока выполняется один из пяти вышеперечисленных случаев, цикл завершается.
-
循环结束的收尾工作
: до тех пор, пока oldStartIdx>oldEndIdx || newStartIdx>newEndIdx (представляющий старый узел или новый узел не был пройден)
Для более интуитивного понимания рассмотрим同级别节点比较
из五种
Детали реализации для случая:
Новый начальный узел и старый начальный узел (вариант 1)
- подобно
情况1符合:(从新旧节点的开始节点开始对比
,oldCh[oldStartIdx]和newCh[newStartIdx]
провестиsameVnode(key和sel相同)
Судя по тому, один ли это узел) - затем выполнить
patchVnode
Найдите разницу между ними и обновите график; если разницы нет, ничего не делайте и завершите цикл oldStartIdx++/newStartIdx++
новый конечный узел и старый конечный узел (вариант 2)
- подобно
情况1不符合
просто судить情况2
, если он совпадает: (Сравните от конечного узла старого и нового узлов,oldCh[oldEndIdx]和newCh[newEndIdx]
сравнить, выполнитьsameVnode(key和sel相同)
Судя по тому, один ли это узел) - воплощать в жизнь
patchVnode
Найдите разницу между ними, обновите представление; если разницы нет, ничего не делайте, завершите цикл oldEndIdx--/newEndIdx--
старый начальный узел/новый конечный узел (вариант 3)
- подобно
情况1,2
Если он не совпадает, он попробует случай 3: (начальный узел старого узла сравнивается с конечным узлом нового узла,oldCh[oldStartIdx]和newCh[newEndIdx]
сравнить, выполнитьsameVnode(key和sel相同)
Судя по тому, один ли это узел) - воплощать в жизнь
patchVnode
Найдите разницу между ними, обновите вид, если разницы нет, ничего не делайте, завершите цикл -
oldCh[oldStartIdx]对应的真实dom
перейти кoldCh[oldEndIdx]对应的真实dom
назад -
oldStartIdx++/newEndIdx--
;
старый конечный узел/новый начальный узел (вариант 4)
- подобно
情况1,2,3
Не встречаются, мы постараемся запустить узел конечный узел и новый узел Case 4 :( Старый узел начинается контраст,oldCh[oldEndIdx]和newCh[newStartIdx]
сравнить, выполнитьsameVnode(key和sel相同)
Судя по тому, один ли это узел) - воплощать в жизнь
patchVnode
Найдите разницу между ними, обновите вид, если разницы нет, ничего не делайте, завершите цикл -
oldCh[oldEndIdx]对应的真实dom
перейти кoldCh[oldStartIdx]对应的真实dom
вперед -
oldEndIdx--/newStartIdx++
;
Найти узел в массиве новых начальных узлов/старых узлов (случай 5)
- Ищите со старого узла, если найдете такой же
newCh[newStartIdx]
Тот же узел (и называемый对应节点[1]
),воплощать в жизньpatchVnode
Найдите разницу между ними, обновите вид, если разницы нет, ничего не делайте, завершите цикл -
对应节点[1]对应的真实dom
перейти кoldCh[oldStartIdx]对应的真实dom
вперед
-
若没有寻找到相同的节点
, затем создайтеnewCh[newStartIdx]
соответствующий узлу真实dom
вставить вoldCh[oldStartIdx]对应的真实dom
вперед newStartIdx++
Далее мы вводим结束循环
последние штрихи(oldStartIdx>oldEndIdx || newStartIdx>newEndIdx)
:
-
新节点的所有子节点
Первый переход (newStartIdx>newEndIdx
), цикл заканчивается -
新节点的所有子节点
Конец обхода - поставить узлы, не соответствующие одному и тому же узлу子节点
удалять
-
旧节点的所有子节点
Первый переход (oldStartIdx>oldEndIdx
), цикл заканчивается -
旧节点的所有子节点
В конце обхода есть еще子节点
вставить в旧节点结束节点
раньше; (источник:newCh[newEndIdx + 1].elm)
, что соответствует旧结束节点的真实dom
, NewEndIdx + 1 совпадает с тем, что совпавший узел должен быть -1, необходимо добавить внутренний узел
Наконец, прикрепите исходный код:
function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
let oldStartIdx = 0; // 旧节点开始节点索引
let newStartIdx = 0; // 新节点开始节点索引
let oldEndIdx = oldCh.length - 1; // 旧节点结束节点索引
let oldStartVnode = oldCh[0]; // 旧节点开始节点
let oldEndVnode = oldCh[oldEndIdx]; // 旧节点结束节点
let newEndIdx = newCh.length - 1; // 新节点结束节点索引
let newStartVnode = newCh[0]; // 新节点开始节点
let newEndVnode = newCh[newEndIdx]; // 新节点结束节点
let oldKeyToIdx; // 节点移动相关
let idxInOld; // 节点移动相关
let elmToMove; // 节点移动相关
let before;
// 同级别节点比较
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (oldStartVnode == null) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left
}
else if (oldEndVnode == null) {
oldEndVnode = oldCh[--oldEndIdx];
}
else if (newStartVnode == null) {
newStartVnode = newCh[++newStartIdx];
}
else if (newEndVnode == null) {
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldStartVnode, newStartVnode)) { // 判断情况1
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
}
else if (sameVnode(oldEndVnode, newEndVnode)) { // 情况2
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right情况3
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left情况4
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}
else { // 情况5
if (oldKeyToIdx === undefined) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);
}
idxInOld = oldKeyToIdx[newStartVnode.key];
if (isUndef(idxInOld)) { // New element // 创建新的节点在旧节点的新节点前
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
}
else {
elmToMove = oldCh[idxInOld];
if (elmToMove.sel !== newStartVnode.sel) { // 创建新的节点在旧节点的新节点前
api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm);
}
else {
// 在旧节点数组中找到相同的节点就对比差异更新视图,然后移动位置
patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm);
}
}
newStartVnode = newCh[++newStartIdx];
}
}
// 循环结束的收尾工作
if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {
if (oldStartIdx > oldEndIdx) {
// newCh[newEndIdx + 1].elm就是旧节点数组中的结束节点对应的dom元素
// newEndIdx+1是因为在之前成功匹配了newEndIdx需要-1
// newCh[newEndIdx + 1].elm,因为已经匹配过有相同的节点了,它就是等于旧节点数组中的结束节点对应的dom元素(oldCh[oldEndIdx + 1].elm)
before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
// 把新节点数组中多出来的节点插入到before前
addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
}
else {
// 这里就是把没有匹配到相同节点的节点删除掉
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
}
}
}
Роль ключа
- Дифференциальная операция может
更加快速
; - Операция Diff может быть более точной;
(避免渲染错误)
-
不推荐使用索引
как ключ
Ниже мы рассмотрим примеры таких действий:
Операция Diff может быть более точной;(避免渲染错误)
:
Пример: вставьте элемент z между b и c в три элемента dom a, b и c
ключ не установленКогда ключ установлен:
Операция Diff может быть более точной;(避免渲染错误)
Пример: a, b, c три элемента dom, измените атрибут элемента a, а затем добавьте элемент z перед элементом a.
Ключ не установлен:
Поскольку ключ не установлен, значение по умолчанию не определено, поэтому узлы все одинаковые, содержимое текста обновляется, но по-прежнему используется предыдущий dom, поэтому на самом делеa->z(a原本打勾的状态保留了,只改变了text),b->a,c->b,d->c
, после обхода выясняется, что нужно добавить dom, а в конце добавляется dom элемент с текстом d
ключ установлен:
Когда ключ установлен, a, b, c, d имеют соответствующие ключи,a->a,b->b,c->c,d->d
, тот же контент обновлять не нужно, обход заканчивается, и добавляется элемент dom с текстом z
不推荐使用索引
как ключ:
Установить индекс как ключ:
Это явно неэффективно, мы хотим узнать только разные обновления узла, а использование индекса в качестве ключа увеличит время работы, мы можем решить эту проблему, установив ключ, чтобы он согласовывался с текстом узла:
наконец
Если описание неверно или неясно, свяжитесь со мной в комментариях ниже, я немедленно обновлю его, если у вас есть какие-либо достижения, пожалуйста, поставьте мне лайк👍 Это отличная поддержка для меня, спасибо