zoukankan      html  css  js  c++  java
  • vitual dom实现(转)

    1. 通过JavaScript来构建虚拟的DOM树结构,并将其呈现到页面中;

    2. 当数据改变,引起DOM树结构发生改变,从而生成一颗新的虚拟DOM树,将其与之前的DOM对比,将变化部分应用到真实的DOM树中,即页面中。

    为什么要使用vitual dom?(转自:https://www.jianshu.com/p/616999666920)

    当你用传统的源生api或jQuery去操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。比如当你在一次操作时,需要更新10个DOM节点,理想状态是一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。
    操作DOM的代价仍旧是昂贵的,频繁操作还是会出现页面卡顿,影响用户的体验。
    虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有10次更新DOM的动作,虚拟DOM不会立即执行DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性patch到DOM树上,通知浏览器去执行绘制工作,这样可以避免大量的无谓的计算量。

    一、构建虚拟DOM

    虚拟DOM,其实就是用JavaScript对象来构建DOM树,

    通过JavaScript,我们可以很容易构建它,如下:

    var elem = Element({
                    tagName: 'ul',
                    props: {'class': 'list'},
                    children: [
                        Element({tagName: 'li', children: ['item1']}),
                        Element({tagName: 'li', children: ['item2']})
                    ]
                });
    // Element构造函数
    function
    Element({tagName, props, children}){ if(!(this instanceof Element)){ return new Element({tagName, props, children}) } this.tagName = tagName; this.props = props || {}; this.children = children || []; }

    通过Element我们可以任意地构建虚拟DOM树了。但是有个问题,虚拟的终归是虚拟的,我们得将其呈现到页面中。

    怎么呈现呢?

    从上面得知,这是一颗树嘛,那我们就通过遍历,逐个节点地创建真实DOM节点:

      1. createElement;

      2. createTextNode.

    怎么遍历呢?

    因为这是一颗树嘛,对于树形结构无外乎两种遍历:

      1. 深度优先遍历(DFS)

      2. 广度优先遍历(BFS)

    下面我们就来回顾下《数据结构》中,这两种遍历的思想:

    1. DFS利用栈来遍历数据

    2. BFS利用队列来遍历数据

    (为了将孩子节点append到父节点中,采用DFS)

    Element.prototype.render = function(){
        var el = document.createElement(this.tagName),
            props = this.props,
            propName,
            propValue;
        for(propName in props){
            propValue = props[propName];
            el.setAttribute(propName, propValue);
        }
        this.children.forEach(function(child){
            var childEl = null;
            if(child instanceof Element){
                childEl = child.render();
            }else{
                childEl = document.createTextNode(child);
            }
            el.appendChild(childEl);
        });
        return el;
    };

    此时,我们就可以轻松地将虚拟DOM呈现到指定真实DOM中。假设,我们将上诉ul虚拟DOM呈现到页面body中,如下:

    var elem = Element({
                    tagName: 'ul',
                    props: {'class': 'list'},
                    children: [
                        Element({tagName: 'li', children: ['item1']}),
                        Element({tagName: 'li', children: ['item2']})
                    ]
                });
    document.querySelector('body').appendChild(elem.render());

    二、处理DOM更新

    在前一小结,我们成功地实现了虚拟DOM,并将其转化为真实DOM,呈现在页面中。

    接下来,我们就处理当DOM更新时,怎样通过新旧虚拟DOM对比,然后将变化部分更新到真实DOM中的问题。

    DOM更新,无外乎四种情况,如下:

      1. 新增节点;

      2. 删除节点;

      3. 替换节点;

      4. 父节点相同,对比子节点.

    毫无疑问,遍历DOM树仍然采用DFS遍历。

    因为我们要将变化的节点更新到真实DOM中,所以还得传入真实的DOM根节点,并且真实的DOM节点与虚拟的DOM节点,树形结构一致,故通过标记可以记录节点变化位置,如下:

    实现函数如下:

    复制代码
    function updateElement($root, newElem, oldElem, index = 0) {
        if (!oldElem){
            $root.appendChild(newElem.render());
        } else if (!newElem) {
            $root.removeChild($root.childNodes[index]);
        } else if (changed(newElem, oldElem)) {
            if (typeof newElem === 'string') {
                $root.childNodes[index].textContent = newElem;
            } else {
                $root.replaceChild(newElem.render(), $root.childNodes[index]);
            }
        } else if (newElem.tagName) {
            let newLen = newElem.children.length;
            let oldLen = oldElem.children.length;
            for (let i = 0; i < newLen || i < oldLen; i++) {
                updateElement($root.childNodes[index], newElem.children[i], oldElem.children[i], i)
            }
        }
    }
    复制代码

    其中的changed方法,简单实现如下:

    function changed(elem1, elem2) {
        return (typeof elem1 !== typeof elem2) ||
               (typeof elem1 === 'string' && elem1 !== elem2) ||
               (elem1.type !== elem2.type);
    }

    好了,一个简单的虚拟DOM就实现了。

    三、效果展示

    通过JS构建一颗虚拟DOM(如上诉ul),并将其呈现到页面中,然后替换其子节点,动态更新到真实DOM中,如下:

    <body>
            <button id="refresh">refresh element</button>
            <div id="root"></div>
            <script src="./virtualDom.js"></script>
            <script>
                var elem = Element({
                    tagName: 'ul',
                    props: {'class': 'list'},
                    children: [
                        Element({tagName: 'li', children: ['item1']}),
                        Element({tagName: 'li', children: ['item2']})
                    ]
                });
                var newElem =  Element({
                    tagName: 'ul',
                    props: {'class': 'list'},
                    children: [
                        Element({tagName: 'li', children: ['item1']}),
                        Element({tagName: 'li', children: ['hahaha']})
                    ]
                });
                var $root = document.querySelector('#root');
                var $refresh = document.querySelector('#refresh');
                updateElement($root, elem);
                $refresh.addEventListener('click', () => {
                    updateElement($root, newElem, elem);
                });
            </script>
        </body>
  • 相关阅读:
    eval()和JSON.parse()的区别
    Android的Monkey和MonkeyRunner
    JavaScript event对象clientX,offsetX,screenX异同
    @-webkit-keyframes 动画 css3
    W3C标准定义的DOM由哪三部分组成
    Javascript 中的神器——Promise
    为什么你的javascript学了这么久,水平还是烂成了渣?
    js深入理解构造函数和原型对象
    js常用面试题整理
    面试宝典:25个最经典的JavaScript面试题及答案
  • 原文地址:https://www.cnblogs.com/ceceliahappycoding/p/10769011.html
Copyright © 2011-2022 走看看