zoukankan      html  css  js  c++  java
  • 虚拟 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);
            // 替换
        }
  • 相关阅读:
    python学习手册笔记——10.python语句简介
    python学习手册笔记——09.元组、文件及其他
    python学习手册笔记——07.字符串
    python学习手册笔记——04.介绍python对象类型
    python学习手册笔记——03.如何运行程序
    python学习手册笔记——02.python如何运行程序
    python学习手册笔记——00.序言
    Scrum-中文翻译
    黑天鹅——笔记
    04.敏捷回顾——预设会议基调的活动笔记
  • 原文地址:https://www.cnblogs.com/iceflorence/p/8944148.html
Copyright © 2011-2022 走看看