zoukankan      html  css  js  c++  java
  • 浅谈 Virtual DOM 的那些事

    背景

    我们都知道频繁的dom给我们带来的代价是昂贵的,例如我们有时候需要去更新Table 的部分数据,必须去重新重绘表格,这代价实在是太大了,相比于频繁的手动去操作dom而带来性能问题,vdom很好的将dom做了一层映射关系,进而将在我们本需要直接进行dom的一系列操作,映射到了操作vdom.

      

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>virtualDom</title>
    </head>
    <body>
    <div id="container"></div>
    <button id="btn-change">修改</button>
    <script type="application/javascript" src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script type="application/javascript">
        const dataSource = [{
            key: '1',
            name: '胡彦斌',
            age: 32,
            address: '西湖区湖底公园1号'
        }, {
            key: '2',
            name: '胡彦祖',
            age: 42,
            address: '西湖区湖底公园1号'
        }];
    
        const columns = [{
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
        }, {
            title: '年龄',
            dataIndex: 'age',
            key: 'age',
        }, {
            title: '住址',
            dataIndex: 'address',
            key: 'address',
        }];
       function render(data) {
           var container = $('#container');
           container.html(''); //清空容器
           //添加表头
           var $table =$('<table>')
           $table.append($('<tr>'))
           columns.map(function(item,index){
               $table.append($('<td>'+item.title+'</td>'))
           })
           $table.append($('</tr>'))
           //添加表体
           dataSource.forEach(function(item){
               $table.append($('<tr></tr><td>'+item.name+'</td>'+'<td>'+item.age+'</td>'+'<td>'+item.address+'</td></tr>'))
           })
           //只渲染一遍dom,尽然如此,还是需要清空容器
           container.append($table)
       }
       $('#btn-change').click(function(){
           dataSource[0].name="胡军网";
           dataSource[1].address='南山区沙河东路1号'
           //re——render
           render(dataSource)
       })
       render()
    </script>
    </body>
    </html>
    

    解决

    1. virtual dom,虚拟 DOM
    2. 用 JS 模拟 DOM

    什么是vdom

    HTML DOM 结构:

    <ul id="ul-list">
        <li class="item">Item 1</li>
        <li class="item">Item 2</li>
        <li class="item">Item 3</li>
    </ul>

    针对于上面HTML DOM 结构,可以用JS表示为:

    var ulE = {
        tagName: 'ul', // 标签名
        props: { // 属性用对象存储键值对
            id: 'ul-list'
        },
        children: [ // 子节点
            {tagName: 'li', props: {className: 'item'}, children: ["Item 1"]},
            {tagName: 'li', props: {className: 'item'}, children: ["Item 2"]},
            {tagName: 'li', props: {className: 'item'}, children: ["Item 3"]},
        ]
    }

     JS对象中抽取公共的部分属性,进一步封装:

    export default Ele = (tagName, props, children) => {
        this.tagName = tagName
        this.props = props
        this.children = children
    }  

      

    import * as el from 'ele';
    var ol = el('ul', {id: 'ul-list'}, [
        el('li', {className: 'item'}, ['Item 1']),
        el('li', {className: 'item'}, ['Item 2']),
        el('li', {className: 'item'}, ['Item 3'])
    ]);
    


    通过snabbdom进行virtual dom(核心API:h函数、patch函数)

    案例一: 对比局部更新添加修改ul中的li

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>snabbdom</title>
    </head>
    <body>
    <div id="container"></div>
    <button id="btn-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 type="application/javascript">
        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');
        //定义 virtual node
        var vnode = h('ul#ul-list',{},[
            h('li.item',{},'item1'),
            h('li.item',{},'item2')
        ])
        patch(container,vnode);
        document.getElementById('btn-change').addEventListener('click',function () {
            var newVnode = h('ul#ul-list',{},[
                h('li.item',{},'item1'),
                h('li.item',{},'西湖区湖底公园1号'),
                h('li.item',{},'西湖区湖底公园2号'),
                h('li.item',{},'西湖区湖底公园3号')
            ])
            patch(vnode,newVnode);
        })
    </script>
    </body>
    </html>
    

      item1 所在的li不会进行dom渲染,只有新增或者修改的node才会发生改变,执行结果如下所示:

    案例二: 局部更新部分Table 数据(使用Vitual DOM 性能的提升)

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>snabbdom</title>
    </head>
    <body>
    <div id="container"></div>
    <button id="btn-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 type="application/javascript">
        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');
        const dataSource = [{
            key: '1',
            name: '胡彦斌',
            age: 32,
            address: '西湖区湖底公园1号'
        }, {
            key: '2',
            name: '胡彦祖',
            age: 42,
            address: '西湖区湖底公园1号'
        }];
    
        const columns = [{
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
        }, {
            title: '年龄',
            dataIndex: 'age',
            key: 'age',
        }, {
            title: '住址',
            dataIndex: 'address',
            key: 'address',
        }];
        var vdom=null;
        function render(dataSource) {
            var titleTr= [];
            titleTr.push(h('td',{},' '))
            columns.forEach(function(item){
               if(item.hasOwnProperty('title')){
                   titleTr.push(h('td',{},item['title']))
               }
            })
            var vTitle = h('tr',{},titleTr);
            var vBody =dataSource.map(function(item){
                const vp= []
                for(var i in item) {
                    if(item.hasOwnProperty(i)){
                        vp.push(h('td',{},item[i]))
                    }
                }
                return h('tr',{},vp)
            })
            vBody.unshift(vTitle);
            var vTable = Object.assign([],vBody);
            var newVnode = h('table',{},vTable)
            if(!vdom){
                vdom = newVnode;
                patch(container,vdom);
            }else{
                patch(vdom,newVnode);
            }
        }
        document.getElementById('btn-change').addEventListener('click',function () {
            dataSource[0].name="胡军网";
            dataSource[1].address='南山区沙河东路1号'
            //re——render
            render(dataSource)
        })
        render(dataSource)
    </script>
    </body>
    </html>
    

      执行结果如下所示:

    patch函数——patch(container,vDom)过程的简单实现

    /**
     *
     * @param container 容器
     * @param vDom 虚拟dom
     * @constructor
     */
    
    var ulE = {
        tagName: 'ul', // 标签名
        props: { // 属性用对象存储键值对
            id: 'ul-list'
        },
        children: [ // 子节点
            {tagName: 'li', props: {className: 'item'}, children: ["Item 1"]},
            {tagName: 'li', props: {className: 'item'}, children: ["Item 2"]},
            {tagName: 'li', props: {className: 'item'}, children: ["Item 3"]},
        ]
    }
    
    export default function VDomCreateElement(vDom){
        var tagName=vDom.tagName || '';
        var props =vDom.props || {};
        var children =vDom.children || [];
        var tagNameEle =document.createElement(tagName);
        for(var prop in props){
            if(props.hasOwnProperty(prop)){
                tagNameEle.setAttribute(prop,props[prop])
            }
        }
        if(!children){
            return tagNameEle;
        }else{
            children.forEach(function(item){
                tagNameEle.appendChild(VDomCreateElement(item)) //不断递归生成child Node
            })
        }
        return tagNameEle;
    }

    patch函数——patch(vDom,newVDom)过程的简单模拟实现

      

    /**
     * vDOM 简单diff 对比 新的dom渲染到旧的dom
     * @param vDom 老vDom
     * @param newVDom 新vDom
     */
    export function vDomDiff(vDom,newVDom){
        var vDomChilden = vDom.children || [];
        var newVDomChilden = newVDom.children || [];
        //假设 tagName 相同
        vDomChilden.forEach(function(item,index){
            if(!newVDomChilden[index]){
                return;
            }
            if(item.tagName === newVDomChilden[index].tagName){
                //两者tagName 一样 递归
                VDomCreateElement(item,newVDomChilden[index]);
            }else {
                //两者tagName 不一样 替换
                replaceNode(item,newVDomChilden[index])
            }
        })
    }
    
    /**
     * dom操作 替换
     * @param vDom
     * @param newVDom
     */
    function replaceNode(vDom,newVDom){
        //dom操作 node替换
        // ....
    
    }
    

      

    Visual DOM 为何使用diff算法

     Visual DOM找出DOM 中不同,进而更新DOM,diff算法同样也是找出文件中的不同进行对比,diff应用在linux,git……,

    源码地址

    https://github.com/10086XIAOZHANG/VirtualDOMDemo  

  • 相关阅读:
    程序的机器级表示(一)
    virtual memory(1)
    Python定义参数数量可变的method的问题
    存储器结构层次(四)
    CAShapeLayer
    cell
    远程服务器推送
    keyboad
    search搜索
    Cocoopod
  • 原文地址:https://www.cnblogs.com/fuGuy/p/9220106.html
Copyright © 2011-2022 走看看