предисловие
В настоящее время хорошо известные React и Vue используют виртуальный дом, Благодаря эффективному алгоритму сравнения Virtual DOM позволяет нам больше не заботиться о проблемах с производительностью и может изменять состояние данных по своему усмотрению. В реальной разработке нам не нужно заботиться о том, как реализован виртуальный DOM, но действительно необходимо понимать принцип реализации Virtual DOM. Эта статья ссылается на исходный код https://github.com/livoras/simple-virtual-dom, чтобы понять виртуальный DOM.
Если вы считаете, что я хорошо пишу, пожалуйста, помогите мне.star
1. Управление состоянием внешнего интерфейса
Во все более сложных клиентских приложениях часто упоминается управление состоянием.От ранней эры slash-and-burn до jQuery, а теперь и до популярной эры MVVM форма управления состоянием претерпела коренные изменения. и нам больше не нужно поддерживать. Существует множество обратных вызовов событий и мониторинга для обновления представления, и вместо использования двусторонней привязки данных представление может автоматически обновляться только путем поддержания соответствующего состояния данных, что значительно улучшает разработку эффективность.
Однако двухсторонняя привязка данных — не единственный способ, есть еще очень грубый и эффективный способ: как только данные изменились, перерисовать все представление, то есть сбросить innerHTML. Этот подход действительно прост, груб и эффективен, но если все представление обновляется только потому, что изменяются небольшие локальные данные, рентабельность слишком низкая, а события, поля ввода, которые получают фокус, и т. д. должны быть повторно обработаны. . Поэтому для небольших приложений или частичных небольших представлений это вполне возможно, но для сложных крупномасштабных приложений такой подход нецелесообразен. Таким образом, мы можем использовать JavaScript для имитации дерева DOM, использовать только что отрисованное дерево объектов для сравнения со старым деревом, записывать изменения, а затем применять их к реальному дереву DOM, так что нам нужно только изменить исходное представление. места без необходимости повторного рендеринга сразу. В этом преимущество виртуального DOM.
2. Просмотр визуализации
По сравнению с объектами DOM нативные объекты JavaScript быстрее и проще в обработке. Информация о структуре и атрибутах дерева DOM может быть представлена с помощью JavaScript, например:
var element = {
tagName: 'ul', // 节点标签名
props: { // dom的属性键值对
id: 'list'
},
children: [
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]}
]
}
Тогда результат рендеринга в html:
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
Теперь, когда информация дерева DOM может быть представлена с помощью JavaScript, дерево DOM можно построить с помощью JavaScript.
Однако просто построить DOM-дерево бесполезно. Нам нужно преобразовать DOM-дерево, построенное с помощью JavaScript, в реальное DOM-дерево. Выразить DOM-узел с помощью JavaScript очень просто. Нам нужно только записать его тип узла и атрибут пара ключ-значение. , дочерние узлы:
function Element(tagName, props, children) {
if(!(this instanceof Element)){
return new Element(tagName, props, children);
}else{
this.tagName = tagName;
this.props = props;
this.children = children;
}
}
Затем тег ul мы можем использовать таким образом для представления
var ul = new Element('ul', {id: 'list'}, [
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]}
])
Сказав так много, он всего лишь структура, представленная JavaScript, так как же преобразовать ее в настоящую структуру DOM:
Element.prototype.render = function() {
let el = document.createElement(this.tagName), // 节点名称
props = this.props // 节点属性
for (var propName in props) {
propValue = props[propName]
el.setAttribute(propName, propValue)
}
this.children.forEach((child) => {
var childEl = (child.hasOwnProperty('tagName')) ? new Element(child.tagName,child.props,child.children).render() : document.createTextNode(child);
el.appendChild(childEl)
})
return el
}
Если мы хотим отобразить ul в структуре DOM, нам просто нужно
ulRoot = ul.render()
document.appendChild(ulRoot)
Таким образом, рендеринг ul в DOM завершается, и получается реальная структура DOM.
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
3. Сравните различия виртуального дерева DOM
Основным алгоритмом React является алгоритм сравнения (здесь имеется в виду оптимизированный алгоритм).Давайте посмотрим, как реализован алгоритм сравнения:
diff будет сравнивать только узлы DOM в пределах одного цветового поля, то есть все дочерние узлы одного и того же родительского узла. Когда обнаруживается, что узел не существует, узел и дочерние узлы будут полностью удалены без дальнейшего сравнения.
В реальном коде старое и новое деревья глубоко просматриваются, и каждый узел помечен. Затем, сравнивая старые и новые деревья, запишите разные места.
// diff 算法,对比两棵树
function diff(oldTree, newTree) {
var index = 0 // 当前节点的标志
var patches = {} // 记录每个节点差异的地方
dfsWalk(oldTree, newTree, index, patches)
return patches
}
function dfsWalk(oldNode, newNode, index, patches) {
// 对比newNode和oldNode的差异地方进行记录
patches[index] = [...]
diffChildren(oldNode.children, newNode.children, index, patches)
}
function diffChildren(oldChildren, newChildren, index, patches) {
let leftNode = null
var currentNodeIndex = index
oldChildren.forEach((child, i) => {
var newChild = newChildren[i]
currentNodeIndex = (leftNode && leftNode.count) // 计算节点的标记
? currentNodeIndex + leftNode.count + 1
: currentNodeIndex + 1
dfsWalk(child, newChild, currentNodeIndex, patches) // 遍历子节点
leftNode = child
})
}
Например:
На рисунке если есть разница в div, отмеченном как 0, то:
patches[0] = [{difference}, {difference}]
Точно так же p — это patches[1], ul — patches[3] и т. д. Патчи относятся к различиям в изменениях.Эти различия включают в себя: 1. Типы узлов разные, и 2. Типы узлов одинаковые, но значения атрибутов разные, и текстовое содержимое другое. Итак, существует несколько видов:
var REPLACE = 0, // replace 替换
REORDER = 1, // reorder 父节点中子节点的操作
PROPS = 2, // props 属性的变化
TEXT = 3 // text 文本内容的变化
Если тип узла другой, значит, его нужно заменить, например, заменить div на section и записать разницу:
patches[0] = [{
type: REPLACE,
node: newNode // section
},{
type: PROPS,
props: {
id: 'container'
}
}]
4. Примените diff к дереву DOM
Информация о реальном дереве DOM построена в заголовке 2, поэтому сначала выполняется обход этого дерева DOM в глубину, и то же самое Объекты патчей сравниваются, обнаруживаются различия, а затем применяются к манипуляциям с DOM.
function patch(node, patches) {
var walker = {index: 0} // 记录当前节点的标志
dfsWalk(node, walker, patches)
}
function dfsWalk(node, walker, patches) {
var currentPatches = patches[walker.index] // 这是当前节点的差异
var len = node.childNodes
? node.childNodes.length
: 0
for (var i = 0; i < len; i++) { // 深度遍历子节点
var child = node.childNodes[i]
walker.index++
dfsWalk(child, walker, patches)
}
if (currentPatches) {
applyPatches(node, currentPatches) // 对当前节点进行DOM操作
}
}
// 将差异的部分应用到DOM中
function applyPatches(node, currentPatches) {
currentPatches.forEach((currentPatch) => {
switch (currentPatch.type) {
case REPLACE:
var newNode = (typeof currentPatch.node === 'string')
? document.createTextNode(currentPatch.node)
: currentPatch.node.render()
node.parentNode.replaceChild(newNode, node)
break;
case REORDER:
reorderChldren(node, currentPatch.moves)
break
case PROPS:
setProps(node, currentPatch.props)
break
case TEXT:
if (node.textContent) {
node.textContent = currentPatch.content
} else {
node.nodeValue = currentPatch.content
}
break
default:
throw new Error('Unknown patch type ' + currentPatch.type)
}
})
}
На этот раз грубый виртуальный дом в основном реализован, а конкретная ситуация сложнее. Но этого нам достаточно, чтобы понять виртуальный дом. Конкретный код с парсингом загружен наgithub
6. Ссылки
Блог Woohoo.cn on.com/Говорят, что откровенно/arc… GitHub.com/Ли ВО РАН/Но... GitHub.com/has8you/blog/is… medium.com/@смертное настроение/… woohoo.info Q.com/talent/articles…