Virtual DOM

468 阅读3分钟

一、React Dom Diff(协调算法)

React的DOM-Diff算法采用深度优先遍历的方式进行,为了提高性能,采用了以下优化策略。

1. 分层比较策略

React的Diff算法采用分层比较策略,只会对同一层级的节点进行比较。这种策略将算法复杂度从O(n³)优化到O(n),其中n是树中节点的数量。

2. 节点类型比较

  • 类型不同:如果节点类型不同,React会直接销毁整个旧子树并构建新子树

  • 类型相同:如果节点类型相同,React会比较两者的属性,然后递归比较子节点

3. 列表节点的Key优化

在处理列表时,React使用key属性来识别哪些元素是新增、移动或删除的:

  • 没有key时,React会按顺序比较,可能导致性能问题

  • 有key时,React可以准确识别节点身份,最小化DOM操作

4. 组件类型判断

  • 相同组件类型:更新props并递归比较子节点

  • 不同组件类型:卸载旧组件,挂载新组件

5. 递归子节点比较

React使用双指针算法优化子节点比较:

  • 第一遍遍历:识别需要删除的节点

  • 第二遍遍历:识别需要新增和移动的节点

6. 生成和应用补丁

最终,React收集所有差异并生成最小化的DOM操作指令,批量应用到真实DOM上,减少重排和重绘。

7、列表中的子元素:

在处理列表(如ul或ol)中的子元素时,React需要为每个子元素分配一个唯一的key属性。当列表发生变化时,React通过比较子元素的key值来确定哪些元素需要添加、删除或更新。为了获得最佳性能,key值应该是稳定且唯一的,例如数据的ID。 

8、应用DOM-DIFF算法原理优化代码 

尽量避免跨层级移动 ;

通过设置key优化元素移动时的性能 ;

尽量减少组件的嵌套层级,过深的层级会加大遍历深度,降低性能 ;

合理使用shouldComponentUpdate和PureComponent减少DIFF次数。

参考:

浅谈React中虚拟DOM、diff算法、fiber架构的关系(面试可用)

让虚拟DOM和DOM-diff不再成为你的绊脚石

React diff 算法与其他框架对比

深入框架本源系列 —— Virtual Dom

二、Vue DOM Diff

参考:

Vue.js从Virtual DOM映射到真实DOM的过程

Vue源码分析系列四:Virtual DOM

带你搞懂Vue虚拟Dom和diff算法

我让虚拟DOM的diff算法过程动起来了

都2020年了,我还不懂虚拟DOM

面试官问: 如何理解Virtual DOM?

三、面试题

1、给出如下虚拟dom的数据结构,如何实现简单的虚拟dom,渲染到目标dom树

//样例数据
let demoNode = ({
    tagName: 'ul',
    props: {'class': 'list'},
    children: [
        ({tagName: 'li', children: ['douyin']}),
        ({tagName: 'li', children: ['toutiao']})
    ]
});
复制代码

//构建一个render函数,将demoNode对象渲染为以下dom

<ul class="list">
    <li>douyin</li>
    <li>toutiao</li>
</ul>
复制代码

看到虚拟DOM,是不是感觉很玄乎,但是剥开它华丽的外衣,也就那样:

  1. 通过JavaScript来构建虚拟的DOM树结构,并将其呈现到页面中;
  2. 当数据改变,引起DOM树结构发生改变,从而生成一颗新的虚拟DOM树,将其与之前的DOM对比,将变化部分应用到真实的DOM树中,即页面中。 通过上面的介绍,下面,我们就来实现一个简单的虚拟DOM,并将其与真实的DOM关联。

构建虚拟DOM

虚拟DOM,其实就是用JavaScript对象来构建DOM树,如上ul组件模版,其树形结构如下:

DOM树

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

var elem = Element({
    tagName: 'ul',
    props: {'class': 'list'},
    children: [
        Element({tagName: 'li', children: ['item1']}),
        Element({tagName: 'li', children: ['item2']})
    ]
});
复制代码

note:Element为一个构造函数,返回一个Element对象。为了更清晰的呈现虚拟DOM结构,我们省略了new,而在Element中实现。

/*
* @Params:
*     tagName(string)(requered)
*     props(object)(optional)
*     children(array)(optional)
* */
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)

广度优先遍历

针对实际情况,我们得采用DFS,为什么呢?

因为我们得将子节点append到父节点中

好了,那我们采用DFS,就来实现一个render函数吧,如下:

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());

2、Virtual DOM比真实DOM快吗?

Virtual DOM 认知误区

网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么?

参考:

面试官: 你对虚拟DOM原理的理解?

增量 DOM 与虚拟 DOM 的对比使用

从了解到深入虚拟DOM和实现diff算法

DIff算法看不懂就一起来砍我(带图)

diff 算法深入一下?

聊一聊Diff算法(React、Vue2.x、Vue3.x)

15张图,20分钟吃透Diff算法核心原理,我说的!!!

React官方论文