虛擬 DOM

虛擬DOM :virtual dom(如下簡稱vdom,是vue和react的核心),使用比較簡單。css

一,vdom是什麼,爲什麼會存在vdomhtml

1,什麼是vdom:用js模擬DOM結構,DOM操做很是‘昂貴’,DOM變化的對比,放在JS層來作(圖靈完備語言),提升重繪性能vue

需求:根據給出的數據,將該數據展現成一個表格, 隨便修改一個信息, 表格也跟着修改,下面使用jquery實現demo:node

    <div id="container"></div>
    <button id="btn-change">change</button>
    <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script>
    var data = [{
        name: '張三',
        age: '20',
        address: '北京'
    }, {
        name: '李四',
        age: '21',
        address: '上海'
    }, {
        name: '王五',
        age: '22',
        address: '廣州'
    }];

    // 渲染函數
    function render(data) {
        var $container = $('#container');
        // 清空容器,重要
        $container.html('');
        // 拼接table
        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);
    }
    // 修改信息
    $('#btn-change').click(function() {
        data[1].age = 30;
        data[2].address = '深圳';
        // re-render 再次渲染
        render(data)
    })

    // 頁面加載完馬上執行(初次渲染)
    render(data)

 

 

 

遇到的問題:DOM操做是昂貴的,改動後,整個container容器都從新渲染了一遍,至關於‘推倒重來’,若是項目複雜,很是影響性能react

dom操做的屬性是很是多的,很是複雜,操做很昂貴,因此,儘可能用js代替操做,例:jquery

    var div = document.createElement('div');
    var item, result = '';
    for(item in div) {
        result += '|' + item;
    }
    console.log(result);

 


vdom能夠解決這個問題linux

二,vdom如何應用,核心API是什麼git

1,介紹snabbdom算法

   var vnode = h('ul#list', {}, [
        h('li.item', {}, 'Item 1'),
        h('li.item', {}, 'Item 2')
    ])
    {
        tag: 'ul',
        attrs: {
            id: 'list'
        },
        children: [{
            tag: 'li',
            attrs: { className: 'item' },
            children: ['Item 1']
        }, {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['Item 2']
        }]
    }

 

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

<head>
    <meta charset="UTF-8">
    <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.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.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
    <script>


    var snabbdom = window.snabbdom;

    // 定義patch
    var patch = snabbdom.init([
        snabbdom_class,
        snabbdom_props,
        snabbdom_style,
        snabbdom_eventlisteners
    ])

    // 定義h
    var h = snabbdom.h;
    var container = document.getElementById('container');

    // 生成vnode
    var vnode = h('ul#list', {}, [
        h('li.item', {}, 'Item 1'),
        h('li.item', {}, 'Item 2'),
    ]);
    patch(container,vnode)

    // 模擬改變
    var btnChange = document.getElementById('btn-change');
    btnChange.addEventListener('click', function() {
        var newVnode = h('ul#list', {}, [
            h('li.item', {}, 'Item 1'),
            h('li.item', {}, 'Item 222'),
            h('li.item', {}, 'Item 333'),
        ]);
        patch(vnode,newVnode);
    })
    </script>
</body>

</html>

2,重作以前的demoapp

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

<head>
    <meta charset="UTF-8">
    <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.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.min.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
    <script>
    var snabbdom = window.snabbdom;

    // 定義關鍵函數patch
    var patch = snabbdom.init([
        snabbdom_class,
        snabbdom_props,
        snabbdom_style,
        snabbdom_eventlisteners
    ])

    // 定義關鍵函數 h
    var h = snabbdom.h;
    // 原始數據
    var data = [{
        name: '張三',
        age: '20',
        address: '北京'
    }, {
        name: '李四',
        age: '21',
        address: '上海'
    }, {
        name: '王五',
        age: '22',
        address: '廣州'
    }];
    // 把表頭也放在data中
    data.unshift({
        name: '姓名',
        age: '年齡',
        address: '地址',
    })

    var container = document.getElementById('container');

    
    var vnode;

    function render(data) {
        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] + ''))
                }
            }
            return h('tr',{},tds)
        }))
        if(vnode) {
            // re-render
            patch(vnode,newVnode)
        } else {
            // 初次渲染
            patch(container,newVnode)
        }
        // 存儲當前vnode結果
        vnode = newVnode;

    }

    // 初次渲染
    render(data)

    var btnChange = document.getElementById('btn-change');
    btnChange.addEventListener('click', function() {
        data[1].age = 30;
        data[2].address = '深圳';
        // re-render
        render(data)
    })

</script> </body> </html>

 


3,核心API

h('標籤名',{...屬性...},[...子元素...]) //多個子元素
h('標籤名',{...屬性...},'...') //只有一個子元素
patch(container,vnode) //初次渲染,會把外層容器替代掉
patch(vnode,newVnode) //re-render

三,介紹diff算法(vdom核心算法)

1,vdom爲什麼用diff算法

diff 是linux的基礎命令,能夠比較兩個文本文件的不一樣 git diff xxx;  vdom中應用diff算法是爲了找出須要更新的節點
好比新建兩個文本文件,log1.txt log2.txt

diff log1.txt log2.txt

diff在線對比:http://tool.oschina.net/diff 

使用vdom緣由:DOM操做是昂貴的,所以儘可能減小DOM操做

找出本次DOM必須更新的節點來更新,其餘的不更新
這個找出的過程,就須要diff算法 找出先後兩個vdom的差別

2,diff算法的實現流程

vdom核心函數:h生成dom節點,patch函數-進行對比和渲染的
patch(container,vnode)   初次渲染,會把外層容器替代掉
patch(vnode,newVnode)   re-render

3,如何用vnode生成真是的dom節點

diff實現:
1,patch(container,vnode) 
2,patch(vnode,newVnode) 

核心邏輯:createElement 和 updateChildren

    // patch(container,vnode)
    function createElement(vnode) {
        var tag = vnode.tag;
        var attrs = vnode.attrs || {};
        var children = vnode.children || [];
        if (!tag) {
            return null;
        }
        // 建立真實的DOM元素
        var elem = document.createElement(tag);
        // 屬性
        var attrName;
        for (attrName in attrs) {
            if (attrs.hasOwnProperty(attrName)) {
                // 給elem添加屬性
                elem.setAttribute(attrName, attrs[attrName]);
            }
        }
        // 子元素
        children.forEach(function(childNode) {
            // 遞歸調用 createElement 給elem添加子元素
            elem.appendChild(createElement(childVnode)); //遞歸
        })
        // 返回真實的DOM元素
        return elem;
    }

    // patch(vnode,newVnode)
    function updateChildren(vnode, newVnode) {
        var children = vnode.children || [];
        var newChildren = newVnode.children || [];

        // 遍歷現有的children
        children.forEach(function(child, index) {
            var newChild = newChildren[index];
            if (newChild == null) {
                return;
            }
            if (child.tag === newChild.tag) {
                // 二者tag同樣 深層次對比
                updateChildren(child, newChild);
            } else {
                // 二者tag不同 替換
                replaceNode(child, newChild)
            }
        })
    }

    function replaceNode(vnode, newVnode) {
        var elem = vnode.elem; //真實的DOM節點
        var newElem = createElement(newVnode);
        // 替換
    }
相關文章
相關標籤/搜索