yizihan / blog

Record
1 stars 0 forks source link

Vue - Virtual DOM & Diff #19

Open yizihan opened 6 years ago

yizihan commented 6 years ago

虚拟DOM

DOM操作很慢,操作DOM后会触发浏览器的重排和重绘。

虚拟DOM通过JS模拟DOM结构,来对真实DOM发生的变化保持追踪。

当数据改变时,新生成的Virtual DOM会与旧的Virtual DOM进行对比,通过Diff算法找到区别,这些操作都是在快速的JS中完成的,最后对实际DOM进行最小的DOM操作来完成效果。

// DOM
<div id='parent'>
    <span class="child">item1</span>
</div>

// Virtual DOM
const dom = {
    tagName: 'div',
    props: {
        id: 'parent'
    },
    children: [
        {tagName: 'span', props: {class: 'child'}, children: ['item1']}
    ]
}

将Virtual DOM渲染成DOM

Element.prototype.render = function() {
    // 根据tagName构建真实DOM
    var el = document.createElement(this.tagName)   
    var props = this.props
    // 遍历虚拟DOM中的属性
    for (var propName in props) {
        // 获得属性值
        var propValue = props[propName]
        // 为真实DOM添加属性
        el.setAttribute(propName, propValue)
    }
    var children = this.children || []
    // 遍历子元素
    children.forEach(function(child) {
        var childEl = (child instanceof Element) 
            // 如果还是标签元素,则继续渲染
            ? child.render()
            // 文档元素
            : document.createTextNode(child)
        el.appendChild(childEl)
    })
    // 返回所有渲染后的真实DOM
    return el
}

snabbdom (2018-03-17)

snabbdom 是轻量的 Virtual DOM 实现,代码量少,模块化,结构清晰。

snabbdom 主要的接口有:

示例

// 引入依赖
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>

// 初始化snabbdom
var snabbdom = window.snabbdom;
var patch = snabbdom.init([
        snabbdom_class,
        snabbdom_props,
        snabbdom_style,
        snabbdom_eventlisteners     
]);
var h = snabbdom.h;

var container = document.getElementById('container');
var data = [{name: 'aaa', age: 20}, {name: 'bbb', age: 30}];

var vnode
// 将数据渲染为虚拟DOM函数
function render(data) {
    // 将后台给的data对象调用h(),返回一个虚拟DOM对象
    // re-render时会重新生成,然后和之前生成的Diff
    var newVnode = h('table', {}, data.map(function(item) {
        var tds = []
        var i
        for (i in item) {
            if (item.hasOwnProperty(i)) {
                tds.push(h('td', {}, item[i] + ''))
            }
        }
        // 在table里面添加tr,tr里面包含所有的td及td包含的内容
        return h('tr', {}, tds)
    }))

    if (vnode) {
        // re-render渲染通道
        patch(vnode, newVnode)
    } else {
        // 初次渲染通道
        patch(container, newVnode)
    }
    // 替换vnode的值
    vnode = newVnode
}
// 执行初次渲染
render(data)

var btn = document.getElementById('btn')
btn.addEventListener('click', function() {
    var data = [{name: 'aaa', age: 20}, {name: 'ccc', age: 40}];
    // 只渲染发生修改的地方!!!
    render(data);
})

参考文章:解析 snabbdom 源码

比较innerHTML和Virtual DOM的重绘


DOM Diff

Virtual DOM只会对同一层级的元素进行对比,不会跨层级比较。

设置key值可以最大化的利用节点(可以复用节点)。

// 1.构建虚拟DOM
var Tree = el('div', {'id': 'container'}, [
    el('h1', {style: 'color:blue'}, ['simple virtual dom'])
])

// 2.通过虚拟DOM渲染真正的DOM
var root = tree.render()
document.body.appendChild(root)

// 3.生成新的虚拟DOM
var newTree = el('div', {'di': 'container'}, [
    el('h1', {style: 'color: red'}, ['simple virtual dom'])
])

// 4.比较两个虚拟DOM的不同,记录差异,将不同的地方都放在patches数组
var pathces = diff(Tree, newTree)

// 5.在真正的DOM元素上应用变更
patch(root, patches)