虚拟DOM(转载)

1.为什么需要虚拟DOM

DOM是很慢的,其元素非常庞大,页面的性能问题鲜有由JS引起的,大部分都是由DOM操作引起的。如果对前端工作进行抽象的话,主要就是维护状态和更新视图;而更新视图和维护状态都需要DOM操作。其实近年来,前端的框架主要发展方向就是解放DOM操作的复杂性。

2.理解虚拟DOM

虚拟的DOM的核心思想是:对复杂的文档DOM结构,提供一种方便的工具,进行最小化地DOM操作。这句话,也许过于抽象,却基本概况了虚拟DOM的设计思想

(1) 提供一种方便的工具,使得开发效率得到保证
(2) 保证最小化的DOM操作,使得执行效率得到保证

(1).用JS表示DOM结构

DOM很慢,而javascript很快,用javascript对象可以很容易地表示DOM节点。DOM节点包括标签、属性和子节点,通过VElement表示如下。

//虚拟dom,参数分别为标签名、属性对象、子DOM列表
var VElement = function(tagName, props, children) {
    //保证只能通过如下方式调用:new VElement
    if (!(this instanceof VElement)) {
        return new VElement(tagName, props, children);
    }

    //可以通过只传递tagName和children参数
    if (util.isArray(props)) {
        children = props;
        props = {};
    }

    //设置虚拟dom的相关属性
    this.tagName = tagName;
    this.props = props || {};
    this.children = children || [];
    this.key = props ? props.key : void 666;
    var count = 0;
    util.each(this.children, function(child, i) {
        if (child instanceof VElement) {
            count += child.count;
        } else {
            children[i] = ‘‘ + child;
        }
        count++;
    });
    this.count = count;
}

通过VElement,我们可以很简单地用javascript表示DOM结构。比如

var vdom = velement(‘div‘, { ‘id‘: ‘container‘ }, [
    velement(‘h1‘, { style: ‘color:red‘ }, [‘simple virtual dom‘]),
    velement(‘p‘, [‘hello world‘]),
    velement(‘ul‘, [velement(‘li‘, [‘item #1‘]), velement(‘li‘, [‘item #2‘])]),
]);

上面的javascript代码可以表示如下DOM结构:

<div id="container">
    <h1 style="color:red">simple virtual dom</h1>
    <p>hello world</p>
    <ul>
        <li>item #1</li>
        <li>item #2</li>
    </ul>
</div>

同样我们可以很方便地根据虚拟DOM树构建出真实的DOM树。具体思路:根据虚拟DOM节点的属性和子节点递归地构建出真实的DOM树。见如下代码:

VElement.prototype.render = function() {
    //创建标签
    var el = document.createElement(this.tagName);
    //设置标签的属性
    var props = this.props;
    for (var propName in props) {
        var propValue = props[propName]
        util.setAttr(el, propName, propValue);
    }

    //依次创建子节点的标签
    util.each(this.children, function(child) {
        //如果子节点仍然为velement,则递归的创建子节点,否则直接创建文本类型节点
        var childEl = (child instanceof VElement) ? child.render() : document.createTextNode(child);
        el.appendChild(childEl);
    });

    return el;
}

对一个虚拟的DOM对象VElement,调用其原型的render方法,就可以产生一颗真实的DOM树。

vdom.render();

既然我们可以用JS对象表示DOM结构,那么当数据状态发生变化而需要改变DOM结构时,我们先通过JS对象表示的虚拟DOM计算出实际DOM需要做的最小变动,然后再操作实际DOM,从而避免了粗放式的DOM操作带来的性能问题。

(2).比较两棵虚拟DOM树的差异

在用JS对象表示DOM结构后,当页面状态发生变化而需要操作DOM时,我们可以先通过虚拟DOM计算出对真实DOM的最小修改量,然后再修改真实DOM结构(因为真实DOM的操作代价太大)。

如下图所示,两个虚拟DOM之间的差异已经标红:

为了便于说明问题,我当然选取了最简单的DOM结构,两个简单DOM之间的差异似乎是显而易见的,但是真实场景下的DOM结构很复杂,我们必须借助于一个有效的DOM树比较算法。

设计一个diff算法有两个要点:

如何比较两个两棵DOM树
如何记录节点之间的差异

<1> 如何比较两个两棵DOM树

计算两棵树之间差异的常规算法复杂度为O(n3),一个文档的DOM结构有上百个节点是很正常的情况,这种复杂度无法应用于实际项目。针对前端的具体情况:我们很少跨级别的修改DOM节点,通常是修改节点的属性、调整子节点的顺序、添加子节点等。因此,我们只需要对同级别节点进行比较,避免了diff算法的复杂性。对同级别节点进行比较的常用方法是深度优先遍历:

function diff(oldTree, newTree) {
    //节点的遍历顺序
    var index = 0;
    //在遍历过程中记录节点的差异
    var patches = {};
    //深度优先遍历两棵树
    dfsWalk(oldTree, newTree, index, patches);
    return patches;
}

<2>如何记录节点之间的差异

由于我们对DOM树采取的是同级比较,因此节点之间的差异可以归结为4种类型:

修改节点属性, 用PROPS表示
修改节点文本内容, 用TEXT表示
替换原有节点, 用REPLACE表示
调整子节点,包括移动、删除等,用REORDER表示

对于节点之间的差异,我们可以很方便地使用上述四种方式进行记录,比如当旧节点被替换时:

{type:REPLACE,node:newNode}

而当旧节点的属性被修改时:

{type:PROPS,props: newProps}

在深度优先遍历的过程中,每个节点都有一个编号,如果对应的节点有变化,只需要把相应变化的类别记录下来即可。下面是具体实现:

function dfsWalk(oldNode, newNode, index, patches) {
    var currentPatch = [];
    if (newNode === null) {
        //依赖listdiff算法进行标记为删除
    } else if (util.isString(oldNode) && util.isString(newNode)) {
        if (oldNode !== newNode) {
            //如果是文本节点则直接替换文本
            currentPatch.push({
                type: patch.TEXT,
                content: newNode
            });
        }
    } else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
        //节点类型相同
        //比较节点的属性是否相同
        var propsPatches = diffProps(oldNode, newNode);
        if (propsPatches) {
            currentPatch.push({
                type: patch.PROPS,
                props: propsPatches
            });
        }
        //比较子节点是否相同
        diffChildren(oldNode.children, newNode.children, index, patches, currentPatch);
    } else {
        //节点的类型不同,直接替换
        currentPatch.push({ type: patch.REPLACE, node: newNode });
    }

    if (currentPatch.length) {
        patches[index] = currentPatch;
    }
}

比如对上文图中的两颗虚拟DOM树,可以用如下数据结构记录它们之间的变化:

var patches = {
        1:{type:REPLACE,node:newNode}, //h1节点变成h5
        5:{type:REORDER,moves:changObj} //ul新增了子节点li
    }

(3).对真实DOM进行最小化修改

通过虚拟DOM计算出两颗真实DOM树之间的差异后,我们就可以修改真实的DOM结构了。上文深度优先遍历过程产生了用于记录两棵树之间差异的数据结构patches, 通过使用patches我们可以方便对真实DOM做最小化的修改。

//将差异应用到真实DOM
function applyPatches(node, currentPatches) {
    util.each(currentPatches, function(currentPatch) {
        switch (currentPatch.type) {
            //当修改类型为REPLACE时
            case REPLACE:
                var newNode = (typeof currentPatch.node === ‘String‘)
                 ? document.createTextNode(currentPatch.node)
                 : currentPatch.node.render();
                node.parentNode.replaceChild(newNode, node);
                break;
            //当修改类型为REORDER时
            case REORDER:
                reoderChildren(node, currentPatch.moves);
                break;
            //当修改类型为PROPS时
            case PROPS:
                setProps(node, currentPatch.props);
                break;
            //当修改类型为TEXT时
            case TEXT:
                if (node.textContent) {
                    node.textContent = currentPatch.content;
                } else {
                    node.nodeValue = currentPatch.content;
                }
                break;
            default:
                throw new Error(‘Unknow patch type ‘ + currentPatch.type);
        }
    });
}

 

时间: 2024-10-10 09:44:47

虚拟DOM(转载)的相关文章

Virtual DOM 虚拟DOM的理解(转)

作者:戴嘉华 转载请注明出处并保留原文链接( #13 )和作者信息. 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM树 4.2 步骤二:比较两棵虚拟DOM树的差异 4.3 步骤三:把差异应用到真正的DOM树上 5 结语 6 References 1 前言 本文会在教你怎么用 300~400 行代码实现一个基本的 Virtual DOM 算法,并且尝试尽量把 Virtual DOM 的算法思路阐述清楚.希望在阅读本

什么是虚拟DOM?

(摘抄自一篇文章,觉得这里写得非常不错,所以单独放出来,希望能对大家有帮助.)React为啥这么大?因为它实现了一个虚拟DOM(Virtual DOM).虚拟DOM是干什么的?这就要从浏览器本身讲起 如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶--RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树).这些过程都存在与渲染

vue----核心虚拟dom

一.真实DOM和其解析流程? 浏览器渲染引擎工作流程都差不多,大致分为5步,创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting 第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建). 第二步,用CSS分析器,分析CSS文件和元素上的inline样式,生成页面的样式表. 第三步,将DOM树和样式表,关联起来,构建一颗Render树(这一过程又称为Attachment).每个DOM节点都有attach方法,接受样式信息

虚拟DOM

DOM(Document Object Model)是很慢的,其元素非常庞大,页面的性能问题鲜有由JS引起的,大部分都是由DOM操作引起的.如果对前端工作进行抽象的话,主要就是维护状态和更新视图:而更新视图和维护状态都需要DOM操作. 在jQuery出现以前,我们直接操作DOM结构,这种方法复杂度高,兼容性也较差:有了jQuery强大的选择器以及高度封装的API,我们可以更方便的操作DOM,jQuery帮我们处理兼容性问题,同时也使DOM操作变得简单:MVVM使用数据双向绑定,使得我们完全不需要

React虚拟DOM浅析

转帖: http://www.alloyteam.com/2015/10/react-virtual-analysis-of-the-dom/?hmsr=toutiao.io&bsh_bid=928783684 在Web开发中,需要将数据的变化实时反映到UI上,这时就需要对DOM进行操作,但是复杂或频繁的DOM操作通常是性能瓶颈产生的原因,为此,React引入了虚拟DOM(Virtual DOM)的机制. 什么是虚拟DOM? 虚拟DOM VS 直接操作原生DOM? 虚拟DOM VS MVVM?

深入理解react中的虚拟DOM、diff算法

文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 React中的虚拟DOM与Vue中的虚拟DOM比较 React中的虚拟DOM是什么?   虽然React中的虚拟DOM很好用,但是这是一个无心插柳的结果.   React的核心思想:一个Component拯救世界,忘掉烦恼,从此不再操心界面. 1. Virtual Dom快,有两个前提 1.1 Javascript很快  Chrome刚出来的时候,在Chrome里跑Javascript非

虚拟dom随想

参考资料https://segmentfault.com/a/1190000005769948, https://github.com/DrakeLeung/blog/issues/8, https://github.com/livoras/blog/issues/11 虚拟dom的基本思路: 用 JavaScript 对象结构表示 DOM 树的结构:然后用这个树构建一个真正的 DOM 树,插到文档当中 当状态变更的时候,重新构造一棵新的对象树.然后用新的树和旧的树进行比较,记录两棵树差异 把2

React虚拟DOM具体实现——利用节点json描述还原dom结构

前两天,帮朋友解决一个问题: ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构. 刚好最近在看React,并且了解到其中的虚拟DOM,其实,就是利用json数据来代替DOM结构表示,然后利用这个json数据,渲染出DOM树,总体添加到页面中.下面,我就通过介绍我如何实现上面实际问题的思路,一边完成实际需求,一边实现React中虚拟DOM渲染成DOM的原理. 模拟数据结构如下: 1 var allJson = [{

为什么虚拟DOM更优胜一筹

注意: 虚拟DOM只是实现MVVM的一种方案,或者说是视图更新的一种策略.没有虚拟DOM比MVVM更好一说. 我们回顾传统MVC框架,如backbone,它是将某个模板编译成模板函数,需要更新时,是自己手动将数据整体传入模板函数, 得到一个字符串,使用innerHTML刷新某个容器!注意,这里其实可以优化,但由于是手动,是体力活,都是使用很粗放型的innerhTML了事 (使用jQuery的html方法性能会更差,不过好处是它处理了IE下的innerHTML BUG及全平台的无法执行内部的scr