虚拟DOM
真实DOM 浏览器渲染机制: 构建DOM->构建rule styles->构建render树->布局layout->绘制 虚拟DOM:使用对象模拟真实的DOM树。
为什么需要虚拟DOM 因为操作真实DOM的成本太高。假设要更新DOM树上的一个节点,那么就需要从上到下完全遍历一遍,如果有10个更改点,那么就更改十次DOM。 如果用虚拟DOM的话,一次性更新十个更改点,提升了效率减少了开销。
虚拟DOM的优点在于DIFF算法,什么是DIFF算法?就是比较更新的DOM结点与原来的DOM节点之间的差异。然后更快更准的添加/删除/替换节点。
可以写个DIFF算法吗?暂时还不会,手打复制一次加深下记忆吧。
1.假设真实DOM
<div id="virtual-dom">
<p>Virtual DOM</p>
<ul id="list">
<li class="item">Item 1</li>
<li class="item">Item 2</li>
<li class="item">Item 3</li>
</ul>
<div>Hello World</div>
</div>
2.使用JS对象代表节点,tagName,props表示节点的一些属性
/**
* Element virdual-dom 对象定义
* @param {String} tagName - dom 元素名称
* @param {Object} props - dom 属性
* @param {Array<Element|String>} - 子节点
*/
function Element(tagName, props, children) {
this.tagName = tagName
this.props = props
this.children = children
// dom 元素的 key 值,用作唯一标识符
if(props.key){
this.key = props.key
}
var count = 0
children.forEach(function (child, i) {
if (child instanceof Element) {
count += child.count
} else {
children[i] = '' + child
}
count++
})
// 子元素个数
this.count = count
}
function createElement(tagName, props, children){
return new Element(tagName, props, children);
}
module.exports = createElement;
3.根据element对象的设定,可以将DOM对象设定为:
var el = require("./element.js");
var ul = el('div',{id:'virtual-dom'},[
el('p',{},['Virtual DOM']),
el('ul', { id: 'list' }, [
el('li', { class: 'item' }, ['Item 1']),
el('li', { class: 'item' }, ['Item 2']),
el('li', { class: 'item' }, ['Item 3'])
]),
el('div',{},['Hello World'])
])
DIFF算法代码: DIFF有三种不同:1. patch_text(文本节点替换) 2.patch_replace(直接替换结点) 3.patch_props(节点相同,属性不同) DIFF算法就是收集patch。
// diff 函数,对比两棵树
function diff(oldTree, newTree) {
var index = 0 // 当前节点的标志
var patches = {} // 用来记录每个节点差异的对象
dfsWalk(oldTree, newTree, index, patches)
return patches
}
// 对两棵树进行深度优先遍历
function dfsWalk(oldNode, newNode, index, patches) {
var currentPatch = []
if (typeof (oldNode) === "string" && typeof (newNode) === "string") {
// 文本内容改变
if (newNode !== oldNode) {
currentPatch.push({ type: patch.TEXT, content: newNode })
}
} else if (newNode!=null && oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
// 节点相同,比较属性
var propsPatches = diffProps(oldNode, newNode)
if (propsPatches) {
currentPatch.push({ type: patch.PROPS, props: propsPatches })
}
// 比较子节点,如果子节点有'ignore'属性,则不需要比较
if (!isIgnoreChildren(newNode)) {
diffChildren(
oldNode.children,
newNode.children,
index,
patches,
currentPatch
)
}
} else if(newNode !== null){
// 新节点和旧节点不同,用 replace 替换
currentPatch.push({ type: patch.REPLACE, node: newNode })
}
if (currentPatch.length) {
patches[index] = currentPatch
}
}
知识点都来自于vue核心之虚拟DOM。参考文章地址为 juejin.cn/post/684490…