虚拟DOM
基础概念:
- virtual DOM是对真实DOM的描述和映射
- 当Virtual DOM改变后,我们得到一个新的virtual DOM。使用算法比较新旧两颗Virtual DOM,找到不同之处,仅仅将变化的部分在真实的DOM上进行修改。
一. 抽象DOM树
首先,使用js将DOM结构存储在内存中。
例如:
<ul class="list">
<li>item 1</li>
<li>item 2</li>
</ul>
对应的JS对象
{ type: ‘ul’, props: { ‘class’: ‘list’ }, children: [
{ type: ‘li’, props: {}, children: [‘item 1’] },
{ type: ‘li’, props: {}, children: [‘item 2’] }
] }
转换函数:
使用js 源码比较复杂,涉及树的深搜和广搜和递归等算法。
function h(str){
//..将DOM转换为DOM树后,再根据DOM树建立对应的js对象
return JSobj;
}
可以使用babel jsx(后续学习和使用)
二.应用DOM表达式
根据已有的js表达式,创建真实的DOM
创建节点
创建节点:(接收Virtual DOM(js 对象)返回真实的DOM节点)
function createElement(node) {
if (typeof node === 'string') {
return document.createTextNode(node)
}
const $el = document.createElement(node.type);
node.children /*babel 使用递归,创建相应的子元素*/
.map(createElement)
.forEach($el.appendChild.bind($el));
return $el;
}
三.处理节点变化
使用算法,比较新旧两颗Virtual DOM 树的变化,将变化的部分反映到真实的DOM节点
例如(1):
<ul> <ul>
<li>item1</li> <li>item1</li>
<ul> <li>item2</li>
</ul>
《old tree》 《new tree》
旧Virtual DOM中不存在item2,新Virtual DOM中新增item2,需要使用append添加对应的<li>
.
function updateElement($parent, newNode, oldNode) {
if (!oldNode) {
$parnet.appendChild(
createElement(newNode)
);
}
}
例如(2):
<ul> <ul>
<li>item1</li> <li>item1</li>
<li>item2</li> </ul>
<ul>
《old tree》 《new tree》
新Virtual DOM中不存在item2,需要将对应的<li> 使用remove删除
算法描述:
function updateElement($parent, newNode, oldNode, index = 0) {
if (!oldNode) {
$parent.appendChild(
createElement(newNode)
);
} else if (!newNode) {
$parent.removeChild(
$parnet.childNodes[index]
)
}
}
例如(3):
<ul> <ul>
<li>item1</li> <li>item1</li>
<li>item2</li> <button>submit</button>
<ul> <ul>
《old tree》 《new tree》
新旧Virtual DOM的同一位置,对应节点不同,使用replace替换。
算法描述:
function updateElement($arent, newNode, oldNode, index = 0) {
if (!oldNode) {//旧节点中不存在,将新节点直接添加到父结点上
$parent.appendChild(
createElement(newNode)
);
} else if (!newNode) {
$parent.removeChild(//新Virtual DOM中不存在,直接将该节点在DOM中删除
$parnet.childNodes[index]
);
} else if (changed(newNode, oldNode)) {//同一位置,节点对比
$parent.replaceChild(
createElement(newNode), //节点发生改变,直接进行在DOM上进行替换
$parnet.childNodes[index]
);
}
}
3.子节点的比较
对于节点的操作是基于对每个节点的比较,然后在每个节点上调用updateElement(),对于节点的比较需要用到递归算法。
比较思路:
(1)只有元素节点的子元素需要遍历比较(文本节点的子节点不需要遍历)
(2)把当前节点作为父节点传入
(3)所有的子节点需要遍历比较,即使有些时候可能是undefined,我们的函数可以处理这种情况。
(4)索引(index)也就是children数组的索引。
代码:
function updateElement($parent, newNode, oldNode, index = 0) {
if (!oldNode) {
$parent.appendChild(
createElement(newNode)
);
} else if (!newNode) {
$parent.removeChild(
$parent.childNodes[index]
);
} else if (changed(newNode, oldNode)) {
$parent.replaceChild(
createElement(newNode),
$parent.childNods[index]
);
} else if (newNode.type) {
const newLength = newNode.children.length;
const oldLength = oldNode.children.length;
for (let i = 0; i < newLength || i < oldLength; i++) {
updateElement(
$parent.childNodes[index],
newNode.children[i],
oldNode.children[i],
);
}
}
}
节点对比时:
~新旧Virtual DOM 树对应位置节点比较。
~使用深度遍历方法,并使用index(索引)记录子元素在DOM树中的位置。
四.总结
Virtual DOM 算法
• 用 JavaScript 对象结构表示DOM树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。
• 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
• 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。
Virtual DOM 本质上就是在JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
文档编译者: