Что такое виртуальный дом
Мы знаем, что наши обычные страницы состоят из множества Домов. Что такое виртуальный Дом (виртуальный дом)? Проще говоря, это имитация реальных узлов Домов с помощью JavaScript, а сравнение изменений Домов помещается в слой Js. сделай это.
Ниже приведен традиционный узел dom, с ним должен быть знаком каждый.
tag
представляет имя метки,attrs
Это атрибут dom.Если у каждого dom есть дочерние элементы, он будет отображаться в виде массива в дочерних элементах, и каждый элемент массива представляет собой структуру виртуального dom.
Причина, по которой Js используется здесь для реализации виртуального дома, заключается в том, что Js является единственным в области внешнего интерфейса.Полнота по ТьюрингуТак называемый полный по Тьюрингу язык относится к языку, который может выполнять сложные логические операции и реализовывать различные языки логических алгоритмов.
Зачем использовать виртуальный дом
Некоторые люди спросят: DOM — это очень хорошо. Когда мы впервые изучим внешний интерфейс, мы обязательно столкнемся с JQuery. JQuery — это типичная библиотека фреймворков для работы с DOM. Мы используем JQuery для разработки сцены, чтобы объяснить полезность и стоимость виртуального DOM.
Вот сценарий спроса
var data = [
{
name: '张三',
age: '20',
address: '杭州'
},
{
name: '李四',
age: '22',
address: '北京'
},
{
name: '隔壁老王',
age: '24',
address: "西溪水岸"
}
]
Теперь мы хотим преобразовать эти данные в таблицу и нажать кнопку на странице, чтобы заменить некоторые из наших данных, для этого мы используем Jquery.
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script>
var data = [
{
name: '张三',
age: '20',
address: '杭州'
},
{
name: '李四',
age: '22',
address: '北京'
},
{
name: '隔壁老王',
age: '24',
address: "西溪水岸"
}
]
function render(data) {
var $container = $('#container')
//清空现有内容
$container.html('')
// 拼接 table
var $table = $('<table>')
$table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'))
data.forEach(function (item) {
$table.append($('<tr><td>'+ item.name +'</td><td>'+item.age+'</td><td>'+item.address+'</td></tr>'))
})
// 渲染到页面
$container.append($table)
}
$('#btn-change').click(function () {
data[1].age = 30
data[2].address = '上海'
render(data)
})
// 初始化时候渲染
render(data)
Как видите, мы будемdata
пункта 2age
и пункт 3 изaddress
Данные заменены, нажмите кнопку изменить:
Проблемы, которые решает vdom
Из рисунка видно, что мы изменили только часть данных таблицы, а весьtabel
Все узлы мигают, показывая, что всеtable
Все были заменены.
Эта операция JQuery, основанная на здравом смысле, сильно снижает производительность веб-страницы во времени. Поскольку он изменяет узлы dom, которые не нужно менять, если вы не понимаете серьезности вопроса, вы можете продолжать смотреть вниз.
Работа следующего кода очень проста, создание пустогоdiv
метка, прокрутите в ней свойства и произнесите ее по буквам
var div = document.createElement('div')
var item ,result = ''
for (item in div) {
result += ' | ' + item
}
console.log(result)
Есть плотные атрибуты, не говоря уже о том, что это атрибут только первого уровня. Можно себе представить, насколько трудоемко работать непосредственно с DOM. Работа с DOM трудоемка, но как язык Js работает очень быстро , Слой Js выполняет сравнение DOM и минимизирует ненужные операции с DOM, вместо того, чтобы обновлять его каждый раз, наша эффективность будет значительно увеличена. И vdom отлично может решить эту проблему.
Как пользоваться виртуальным домом
Сказав так много о виртуальном доме, некоторые студенты спросят, как использовать виртуальный дом?
Чтобы понять, как использовать vdom, мы можем использовать существующую библиотеку реализации vdom, чтобы понять ее API, а затем понять, как использовать vdom в разработке.
Здесь мы выбираем библиотеку виртуального дома, используемую в Vue2.snabbdom, на следующем рисунке показан пример перехвата его домашней страницы на github:
После тщательного наблюдения мы можем обнаружить, что в этом официальном случае snabbdom основное содержание состоит из двух функций:h函数
а такжеpatch函数
h функция
можно увидетьh
функция с тремя параметрами
- Селектор тегов
- Атрибуты
- дочерний узел
например первыйh函数
Сгенерированный vnode представляет собойdiv
Этикетка, привязка события щелчкаsomeFn
, первый ребенок со стилемspan
,sapn
является текстовым узломThis is bold
, второй потомок — это непосредственно текстовый узел, а третий — этоherf
изa
Связь
патч функция
patch
на два случая
- Первый — при первом рендеринге.
patch
перетащите vnode наcontainer
в пустой тареvar vnode = h('ul#list',{},[ h('li.item',{},'大冰哥'), h('li.item',{},'伦哥'), h('li.item',{},'阿孔') ]) patch(container, vnode) // vnode 将 container 节点替换
Когда патч рендерится в первый раз, сгенерированный vnode выбрасывается в пустой контейнер Вы можете сравнить предыдущий Jquery при первом рендеринге таблицы, добавить таблицу html в контейнер
- Второй — при обновлении узлов,
newVnode
БудуoldVnode
заменятьbtn.addEventListener('click',function() { var newVnode = h('ul#list',{},[ h('li.item',{},'大冰哥'), h('li.item',{},'伦哥'), h('li.item',{},'孔祥宇'), h('li.item',{},'小老弟'), ]) patch(vnode, newVnode) })
Патч здесь сравнивает vonde с предыдущим vnode, изменяет только измененные места и сохраняет нетронутые места без изменений.Основой здесь является задействованный алгоритм diff.
Мы можем ясно видеть, что, по сравнению с предыдущим случаем замены всего dom страницы на JQuery, использование vdompathc
Функция модифицирует только те места, где изменился наш относительно старый vnode, а места, которые не изменились, бесполезны (что видно по мерцанию страницы)
Повторить предыдущий случай Jq, используя vdom
vdom ядро апиh
функция иpatch
Функция, которую мы имеем базовое понимание, для того чтобы закрепить свои знания, и мы собираемся использоватьsnabbdom
Повторите наш предыдущий случай JQuery
код напрямую
<div id="container"></div>
<button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.2/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
let container = document.getElementById('container')
let btn = document.getElementById('btn-change')
let snabbdom = window.snabbdom
let patch = snabbdom.init([
snabbdom_class,
snabbdom_props,
snabbdom_style,
snabbdom_eventlisteners
])
let h = snabbdom.h
let data = [
{
name: '张三',
age: '20',
address: '杭州'
},
{
name: '李四',
age: '22',
address: '北京'
},
{
name: '隔壁老王',
age: '24',
address: "西溪水岸"
}
]
data.unshift({
name: '姓名',
age: '年龄',
address: '地址'
})
let vnode
function render(data) {
// 创建虚拟table节点 第三个参数,也就是虚拟table的孩子 应该是虚拟的 行节点
let newVnode = h('table', {}, data.map(item => {
let tds = [] // 列,作为虚拟行的子项
let i
for(i in item) {
if (item.hasOwnProperty(i)) {
tds.push(h('td', {}, item[i]+''))
}
}
return h('tr', {}, tds) // 虚拟行节点的 孩子 应该是虚拟的 列节点
}))
if (vnode) {
patch(vnode,newVnode)
} else {
// 初次渲染
patch(container,newVnode)
}
vnode = newVnode
}
btn.addEventListener('click', function(){
data[1].age = 30,
data[3].name = '一个女孩',
render(data)
})
// 初始化时候渲染
render(data)
</script>
</body>
Код немного длинноват, на самом деле это то, о чем мы говорили ранее, код в основном выполняет следующие действия.
- Введите основной файл snabbdom, инициализируйте функцию h и функцию исправления.
- Когда он загружается в первый раз, суть рендера фактически
patch(container,newVnode)
- нажмите после
change
Создайте новый VNODE, а затемpatch(vnode,newVnode)
здесьrender
Сосредоточьтесь на функции
- Когда создается newVnode, третий параметр является дочерним.
- а также
table
Дети - это узлы строки -
tr
Узел строки также является vnode, который также используется при его регенерации.h
функция, третий параметрtd
столбец vnode -
td
Третий параметр столбца vnode — это непосредственно текстовый узел, он обходит каждый элемент item и проталкивает вtds
в массиве
К этому моменту у вас должно быть общее представление о vdom.На самом деле, это не столько то, что vdom быстрый, точнее сказать, что он не медленный по сравнению со способом свержения dom Jquer.
Суммировать
ядро апи vdom
- h('имя тега', 'атрибут', [дочерний элемент])
- h('имя тега', 'атрибут', 'текст')
- patch(container, vnode)
- patch(oldVnode,newVnode)
Краткое введение в алгоритм diff
что такое алгоритм сравнения
В нашей повседневной работе мы используем алгоритм сравнения много раз.
Например, вы используете git при отправке кодаgit diff
Команды, или какие-то инструменты сравнения кода в интернете, а ядром нашего виртуального дома является алгоритм diff.Как мы упоминали ранее, нам нужно выяснить узлы, которые необходимо обновить, и не перемещать узлы, которые не обновлено. Суть этого заключается в том, как узнать, какие обновления и какие не обновлены.Для этого процесса требуется алгоритм сравнения.
пройти черезpatch
простой дифференциал
Давайте ковать железо, пока горячо, или использовать предыдущую библиотеку snabbdom, чтобы кратко рассказать об общей идее алгоритма diff.В snabbdom diff в основном отражается вpatch
, далее мы рассмотрим два случая patchpatch(container, vnode)
а такжеpatch(vnode, newVnode)
Пространство ограничено (на самом деле возможности ограничены), вот простое объяснение, потому что слишком много всего, когда дело доходит до завершенного алгоритма diff.Если вам интересно, вы можете взглянуть на исходный код придирчивость
patch(container, vnode)
Мы знаем, что процесс этого патча — это процесс добавления vnode (vdom) в пустой контейнер для создания реального dom.Основной поток кода выглядит следующим образом:
function creatElement(vnode) {
let tag = vnode.tag
let attrs = vnode.attrs || {}
let children = vnode.children || []
// 无标签 直接跳出
if (!tag) {
return null
}
// 创建元素
let elem = document.createElement(tag)
// 添加属性
for(let attrName in attrs) {
if (attrs.hasOwnProperty(attrName)) {
elem.setAttribute(arrtName, arrts[attrName])
}
}
// 递归创建子元素
children.forEach((childVnode) => {
elem.appendChild(createElement(childVnode))
})
return elem
}
Упрощенный код прост, разобраться сможет каждый, один из важных моментовСаморекурсивный вызов для создания дочерних узлов, условие окончания
tag
дляnull
Случай
patch(vnode, newVnode)
Этот процесс исправления представляет собой процесс сравнения различий Мы здесь моделируем только простейший сценарий.
Изменен третий пункт и добавлен четвертый пункт
// 简化流程 假设跟标签相同的两个虚拟dom
function updateChildren (vnode, newVnode) {
let children = vnode.children || []
let newChildren = newVnode.children || []
// 遍历现有的孩子
children.forEach((oldChild, index) => {
let newChild = newChildren[index]
if (newChild === null) {
return
}
// 两者tag一样,值得比较
if (oldChild.tag === newChild.tag) {
// 递归继续比较子项
updateChildren(oldchild, newChild)
} else {
// 两者tag不一样
replaceNode(oldChild, newChild)
}
})
}
Точка здесь также рекурсивная, вот просто простой взять
tag
Судить об условиях обновления, на самом деле, фактический намного сложнее, чем это; иreplace
Фактическая работа функции заключается вnewVnode
Вновь сгенерированный реальный DOM заменяет старый DOM, который включает в себя больше собственных операций с DOM, поэтому я не буду вдаваться в подробности.
На этом этапе каждый должен иметь хорошее представление об основной концепции сравнения. Опять же, чтобы облегчить понимание, процесс алгоритма сравнения был значительно упрощен. Фактический алгоритм сравнения намного сложнее, чем приведенный выше. Например. ,
- Добавление и удаление узлов
- Переупорядочение и оптимизация этого процесса
- Изменения узел свойств стилей событий
- А как оптимизировать алгоритм до предела и так далее. .
Кому интересно, может пойти узнать больше.
Суммировать
Прочитав эту статью, учащиеся, которые не понимают виртуальный дом, получат хорошее представление о виртуальном доме и общем понимании алгоритма сравнения. Для достижения такого эффекта, я думаю, эта статья очень ценна. Студенты, которые хотят узнать больше о виртуальном доме или алгоритме сравнения, могут прочитать snabbdompatch.js
Исходный код, углубленное изучение.
Дополнительный ключ Vue
При написании статей я столкнулся с проблемой привязки Vue Key.Вот с этим горячим совместите алгоритм виртуального DOM и DIFF, чтобы узнать ключ в Vue.
ключи в вью
Прежде всего, объяснение официального сайта Vue:
Когда Vue.js использует
v-for
При обновлении отображаемого списка элементов по умолчанию используется стратегия «повторное использование на месте». Если порядок элементов данных изменен, Vue не будет перемещать элементы DOM в соответствии с порядком элементов данных, а просто будет повторно использовать каждый элемент здесь и убедиться, что он показывает каждый элемент, который был отрисован по определенному индексу.
Стратегия мультиплексирования на месте здесь повторно использует элементы, которые не изменились, а остальные должны быть переставлены последовательно.
Чтобы дать Vue подсказку, чтобы он мог отслеживать идентификатор каждого узла и, таким образом, повторно использовать и изменять порядок существующих элементов, вам необходимо предоставить каждому элементу уникальный
key
Атрибуты. идеальныйkey
Значение представляет собой уникальный идентификатор, который имеет каждый элемент.
Мы используем использование часто будем использоватьindex
(то есть нижний индекс массива) какkey
, но на самом деле это метод, который не рекомендуется
Как понять, давайте рассмотрим следующий пример:
Вот массив данных
const list = [
{
id: 1,
name: 'test1',
},
{
id: 2,
name: 'test2',
},
{
id: 3,
name: 'test3',
},
]
Теперь мы хотим добавить линию данных
const list = [
{
id: 1,
name: 'test1',
},
{
id: 2,
name: 'test2',
},
{
id: 3,
name: 'test3',
},
{
id: 4,
name: '添加到最后的一条数据',
},
]
на этот раз использоватьindex
так какkey
, это не проблема, потому чтоindex
1 добавляется сзади
Но если вставленные данные вставляются в середину, а не в конец,
const list = [
{
id: 1,
name: 'test1',
},
{
id: 4,
name: '不甘落后跑到第二的的一条数据',
}
{
id: 2,
name: 'test2',
},
{
id: 3,
name: 'test3',
},
]
В это время произойдет ситуация:
之前的数据 之后的数据
key: 0 index: 0 name: test1 key: 0 index: 0 name: test1
key: 1 index: 1 name: test2 key: 1 index: 1 name: 不甘落后跑到第二的的一条数据
key: 2 index: 2 name: test3 key: 2 index: 2 name: test2
key: 3 index: 3 name: test3
Таким образом, после добавления данных в дополнение к первым данным можно就地复用
, последние три строки должны быть перерисованы, что, очевидно, не является тем результатом, который нам нужен.
использоватьуникальный ключулучшить:
На этот раз мы поставили
key
Привязать к уникальному идентификатору id
之前的数据 之后的数据
key: 1 id: 1 index: 0 name: test1 key: 1 id: 1 index: 0 name: test1
key: 2 id: 2 index: 1 name: test2 key: 4 id: 4 index: 1 name: 不甘落后的一条数据
key: 3 id: 3 index: 2 name: test3 key: 2 id: 2 index: 2 name: test2
key: 3 id: 3 index: 3 name: test3
Помимо добавленияid为4的不甘落后的数据
Он добавлен недавно, а остальные повторно используют предыдущий dom, потому что он передается здесь唯一key
связывать и не перерисовывать при изменении порядка.
Таким образом, нам нужно использовать ключ, чтобы сделать уникальный идентификатор для каждого узла, алгоритм Vue Diff может правильно идентифицировать этот узел, найти правильную область местоположения для вставки нового узла, поэтому в одном предложении:Роль ключа в основном заключается в эффективном обновлении виртуального DOM.
Soul Painter онлайн:
Видно, что когда наши старые данные преобразуются в новые данные [a,b,c,d] --> [a,e,b,c,d]
Если мы не используем правильныйkey
, возможно, в дополнение к данным, которые можно использовать повторно, необходимо повторно отобразить следующие четыре данных.
тогда как если правильныйkey
Когда вы можете понять, что есть только одно место, которое нужно изменить, то есть новые данные e, а остальные будут продолжать использоваться соответственно, как показано стрелками.