阅读 19

虚拟DOM总结

什么是vdom? 为何会存在Vdom?

  1. 用 js 模拟 DOM 结构。
  2. DOM变化的对比放在js层来做。
  3. 提高重绘性能。

真实DOM:

<ul id="list">
    <li class="item">item1</li>
    <li class="item">item2</li>
</ul>
复制代码

js模拟:

{
    tag: 'ul',
    attrs: {
        id: 'list'
    },
    children: [
        {
            tag: 'li',
            attrs: {className: 'item'},
            children: ['item1']   
        },
        {
            tag: 'li',
            attrs: {className: 'item'},
            children: ['item2'] 
        }
    ]
}
复制代码

举一个栗子:

设计一个数据表格,点击按钮更改数据。

dom 操作的写法:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="container"></div>
    <button id="btn-change">数据更改</button>
</body>
<script src="./jquery.min.js"></script>
<script>
    var data = [
        {
            name: 'test1',
            age: '23',
            address: 'shanghai'
        },
        {
            name: 'test2',
            age: '24',
            address: 'beijing'
        },
        {
            name: 'test3',
            age: '25',
            address: 'chengdu'
        }
    ]

    function render(data) {
        var $container = $("#container");
        $container.html('');

        var $table = $('<table>');
        $table.append('<tr><td>name</td><td>age</td><td>address</td></tr>');
        data.forEach(function (item) {
            $table.append(
                '<tr>'
                + '<td>' + item.name + '</td>' 
                + '<td>' + item.age + '</td>' 
                + '<td>' + item.address + '</td>' 
                + '</tr>'
            )
        });

        $container.append($table);
    }

    render(data);

    $('#btn-change').on('click', function () {
        data[1].name = 'yezhiwei';
        data[2].age = '30';
        render(data);
    })
</script>

</html>
复制代码

vdom如何应用,核心API是什么?

  1. snabbdom介绍

  2. 核心api:

    h(),patch()

先看一个栗子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="container"></div>
    <button id="btn-change">change</button>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.min.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
        var vnode = h('ul#list', {}, [
            h('li.item', {}, 'item 1'),
            h('li.item', {}, 'item 2')
        ])
        patch(container, vnode);
        
        document.getElementById('btn-change').addEventListener('click', function(){
            //生成新的vnode
            var newVnode = h('ul#list', {}, [
                h('li.item', {}, 'item 1'),
                h('li.item', {}, 'item b'),
                h('li.item', {}, 'item 3')
            ])
            patch(vnode, newVnode);
        })
    </script>
</html>
复制代码

改写上面dom处理的例子:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="container"></div>
    <button id="btn-change">change</button>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.min.js"></script>
<script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.min.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');
    const btnChange = document.getElementById('btn-change');

    var data = [
        {
            name: 'test1',
            age: '23',
            address: 'shanghai'
        },
        {
            name: 'test2',
            age: '24',
            address: 'beijing'
        },
        {
            name: 'test3',
            age: '25',
            address: 'chengdu'
        }
    ]

    data.unshift({
        name: '姓名',
        age: '年龄',
        address: '地址'
    })

    var vnode;
    var newVnode;
    render(data);
    function render(data){
        newVnode = h('table', {} ,data.map(function(item){
            var tds = [];
            for(var i in item){
                if(item.hasOwnProperty(i)){
                    tds.push(h('td', {}, item[i] + ''))
                }
            }
            return h('tr', {}, tds);
        }))

        if(vnode){
            patch(vnode, newVnode);
        }else{
            patch(container, newVnode);
        }
        vnode = newVnode;
    }

    btnChange.addEventListener('click', function(){
        data[1].name = 'yezhiwei';
        data[2].age = '100';
        render(data);
    })
</script>
</html>
复制代码

打开控制台观察下两种方法的DOM变化时的闪烁情况,第二种方法明显优于第一种方法,只修改了变化的部分。

diff算法

为何使用diff算法,一句话来说明的话:

因为DOM操作比较昂贵,想要减少DOM的操作就必须找出必要更新的节点,而找出的过程就称为----diff算法。

模拟 patch(container, vnode);

function createElement(vnode){
    var tag = vnode.tag,
        attrs = vnode.attrs || {},
        children = vnode.children || []
    if(!tag){
        return null
    }
    
    var elem = document.createElement(tag);
    for(var attr in attrs){
        if(attrs.hasOwnProperty(attr)){
            elem.setAttribute(attr, attrs[attr]);
        }
    }
    
    children.forEach(function(children) {
        elem.appendChild(createElement(children));
    })
    
    return elem
}
复制代码

模拟patch(vnode, newVnode)

function updateChildren(vnode, newVnode){
    var children = vnode.children || [];
    var newChildren = newVnode.children || [];
    children.forEach(function(child, index){
        var newChild = newChildren[index];
        if(!newChild){
            return false
        }
        if(child.tag === newChild.tag){
            updateChildren(child, newChild)
        }else{
            replaceNode(child, newChild)
        }
    })
}
复制代码
评论