导言
说实话,一开始我看到diff算法还是挺抵触的,感觉有点难懂呀。
虚拟DOM
到底什么是虚拟DOM呢?
其实就是使用 js 对象去模拟一个 DOM 结构。
诞生的原因是因为 操作DOM 的代价比较高 , 如果频繁地操作 DOM ,会导致性能的下降 , 所有如果我们把 操作DOM的结果先放入 虚拟DOM中 , 然后操作完后 , 再把 虚拟DOM 渲染成 真实 DOM ,一次性append 到相应的节点中 ,便可以优化性能。
用js 对象模拟一个DOM树
比如 , 真实的 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>
我们用 js 对象来表示 DOM 节点 , 使用对象的属性记录节点的类型,属性,子节点等。
/**
* 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;
根据 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'])
])
但是,页面上并没有这个结构,下一步介绍如何将 虚拟DOM渲染成真实DOM
/**
* render 将virdual-dom 对象渲染为实际 DOM 元素
*/
Element.prototype.render = function () {
var el = document.createElement(this.tagName)
var props = this.props
// 设置节点的DOM属性
for (var propName in props) {
var propValue = props[propName]
el.setAttribute(propName, propValue)
}
var children = this.children || []
children.forEach(function (child) {
var childEl = (child instanceof Element)
? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
: document.createTextNode(child) // 如果字符串,只构建文本节点
el.appendChild(childEl)
})
return el
}
最后,把渲染出来的 DOM ,添加到页面中
ulRoot = ul.render();
document.body.appendChild(ulRoot);
比较两颗 虚拟DOM树的差异---DOM-diff算法
diff算法 用来比较两颗虚拟 DOM树的差异 ,也就是比较两个对象的差异, 时间复杂度是 O(n)
- 前序深度优先遍历
总结
- 用 js 对象模拟 DOM ,生成 虚拟DOM
- 把 虚拟DOM转换成 真实 DOM 并插入页面
- 如果有事件发生修改了虚拟DOM ,使用 diff算法比较 新旧两颗虚拟DOM的差异 ,得到补丁
- 最后把补丁patch 到真实的 DOM上。