虚拟DOM与Dom-diff算法-自我入门

876 阅读2分钟

导言

说实话,一开始我看到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)

  1. 前序深度优先遍历

总结

  1. 用 js 对象模拟 DOM ,生成 虚拟DOM
  2. 把 虚拟DOM转换成 真实 DOM 并插入页面
  3. 如果有事件发生修改了虚拟DOM ,使用 diff算法比较 新旧两颗虚拟DOM的差异 ,得到补丁
  4. 最后把补丁patch 到真实的 DOM上。