虚拟 DOM

虚拟DOM :virtual dom(以下简称vdom,是vue和react的核心),使用比较简单。

一,vdom是什么,为何会存在vdom

1,什么是vdom:用js模拟DOM结构,DOM操作非常‘昂贵’,DOM变化的对比,放在JS层来做(图灵完备语言),提高重绘性能

需求:根据给出的数据,将该数据展示成一个表格, 随便修改一个信息, 表格也跟着修改,下面使用jquery实现demo:

    <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容器都重新渲染了一遍,相当于‘推倒重来’,如果项目复杂,非常影响性能

dom操作的属性是非常多的,非常复杂,操作很昂贵,所以,尽量用js代替操作,例:

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

 


vdom可以解决这个问题

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

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,重做之前的demo

<!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);
        // 替换
    }

 

posted @ 2018-04-25 17:23  水墨墨心  阅读(437)  评论(0编辑  收藏  举报