欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

虚拟 DOM

程序员文章站 2022-05-06 21:34:23
虚拟DOM :virtual dom(以下简称vdom,是vue和react的核心),使用比较简单。 一,vdom是什么,为何会存在vdom 1,什么是vdom:用js模拟DOM结构,DOM操作非常‘昂贵’,DOM变化的对比,放在JS层来做(图灵完备语言),提高重绘性能 需求:根据给出的数据,将该数 ......

虚拟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);

 

虚拟 DOM

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