Skip to content

Vue 的虚拟 DOM

虚拟 DOM (vdom)和 diff

diff 算法是 vdom 中最核心,最关键的部分

  • DOM 操作非常耗费性能
  • 以前使用 jQuery 可以自行手动控制操作 DOM
  • 而 Vue 是数据驱动视图,如何有效控制 DOM 操作?

解决方案 - vdom

  • 有了一定复杂度,想减少计算次数比较难
  • 那么可以把更多计算转移为 JS 计算,因为 JS 执行速度很快
  • vdom - 用 JS 模拟 DOM 结构,计算出最小的变更,操作 DOM

用 JS 模拟 DOM 结构,举个例子有这样一段 html 结构

js
<div id="div1" class="container">
  <p>vdom</p>
  <ul style="font-size: 20px">
    <li>a</li>
  </ul>
</div>

html 可以看成 xml 结构,通过 json 对象来表示,比如

js
{
    tag: 'div',
    props: {
        className: 'container',
        id: 'div1'
    },
    children: [
        {
            tag: 'p',
            children: 'vdom'
        },
        {
            tag: 'ul',
            props: {style: 'font-size: 20px'},
            children: [
                {
                    tag: 'li',
                    children: 'a'
                },
                //...
            ]
        }
    ]
}

通过 snabbdom 学习 vdom

  • 简洁强大的 vdom 库,易学易用
  • Vue 参考它实现的 vdom 和 diff
  • https://github.com/snabbdom/snabbdom
  • Vue 3.0 重新了 vdom 的代码,优化了性能,但 vdom 基本理念不变

snabbdom 库演示

js
<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.3/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
<script>
    const snabbdom = window.snabbdom
    // 定义 patch
    const patch = snabbdom.init([
        snabbdom_class,
        snabbdom_props,
        snabbdom_style,
        snabbdom_eventlisteners
    ])
    // 定义 h
    const h = snabbdom.h
    const container = document.getElementById('container')
    // 生成 vnode
    const vnode = h('ul#list', {}, [
        h('li.item', {}, 'Item 1'),
        h('li.item', {}, 'Item 2')
    ])
    patch(container, vnode)
    document.getElementById('btn-change').addEventListener('click', () => {
        // 生成 newVnode
        const newVnode = h('ul#list', {}, [
            h('li.item', {}, 'Item 1'),
            h('li.item', {}, 'Item B'),
            h('li.item', {}, 'Item 3')
        ])
        patch(vnode, newVnode)
        // vnode = newVnode // patch 之后,应该用新的覆盖现有的 vnode ,否则每次 change 都是新旧对比
    })
</script>

基于 MIT 许可发布