详解react应用中的DOM DIFF算法

前言

对我们搞前端的来说,目前最流行的两大前端框架毫无疑问当属React和Vue,对于这两大框架,想必大家也是再熟悉不过了。然而,这两大框架无一例外的全部放弃使用传统的DOM技术,却采用了以JS为基础的Virtual DOM技术,也可称作虚拟DOM。所以,到底什么是Virtual DOM?两大热门框架全部使用Virtual DOM的原因又是什么?接下来让我这个搞前端的人来好好地为您讲解一下DOM DIFF算法的牛逼之处。

什么是Virtual DOM?

如字面意思所说,Virtual DOM即 虚拟DOM,它的特点就是利用Javascript来模拟DOM结构,并且将DOM的变化对比放在JS层中进行比较。具体操作如下:

// 普通的HTML DOM结构
<ul class="list">
   <li class="item">wjy</li>
   <li class="item">易烊千玺</li>
</ul>

// 映射出的虚拟DOM
{
   tag:'ul',
   props:{
       class:'list'
   },
   children: [
       {
           tag:'li',
           props:{
               class:'item'
           },
           children: ['易烊千玺']
       }
   ]
}

有朋友或许会有点迷惑,普通的HTML DOM结构不好吗?通俗易懂,代码也更加简洁,为什么还需要采用嵌套递归的虚拟DOM形式呢?其实这和普通DOM在重新渲染的过程中非常消耗性能有关,DOM操作看似简便,但其实效率相当低,这是因为如果是在需要频繁修改的真实DOM中,看起来更加复杂的运用JS结构的Virtual DOM效率会更高.

使用Virtual DOM的原因

DOM 渲染页面的操作流程

  • 当浏览器通过域名从服务器拿到对应的HTML文件后,浏览器首先会进行构建DOM树和CSSOM树,关于树的概念,学过数据结构与算法的同学或许对树的节点概念印象深刻。
  • 在HTML DOM中,所有的事物都是节点,而DOM是被视为节点树的HTML,并且,各个节点之间有着相应的层级关系。DOM节点树和HTML中的标签一一对应,构成了DOM树
  • HTML文件

  • HTML DOM树

同样的。在CSS文档中,所有的元素也皆是节点,与HTML中的标签一一对应,构成了CSSOM树 如下图所示

警告! 如果在构建DOM树的过程中,有遇到JS相关的内容时,DOM树的构建会立即停止,这是由于,JS可以对DOM节点进行操作,浏览器为了防止JS会对以完成的DOM造成影响,会阻止DOM树的构建,以节约资源。

在DOM树和CSSOM树不断构建的过程中,渲染树也在逐渐形成,浏览器会根据所构建的渲染树进行网页布局和绘制流程,不断地进行网页的搭建。具体操作流程图如下:

一般而言,对于页面渲染的常规操作,我们通常是操作DOM,修改并重置innerHTML完成页面的渲染,每进行一次DOM的更新操作,都会重新进行一次渲染流程,这个过程包含着页面的重绘和重排。

Virtual DOM的优势

但是如果对于大型页面项目,或者具有多标签,多属性的网页而言,常规的DOM操作实在是太耗时了,每次的简单修改都需要牵动大量的DOM节点的重绘与重排,极大地降低了页面渲染效率.于是,当前端开发人员面对DOM瓶颈一筹莫展的时候,Virtual DOM显示出了作为轻量级的JavaScript对象的极大优越性,顺利得到了了前端开发者的青睐。

在页面进行重新渲染的时候,Virtual DOM进行dom diff计算对比两次并发现其中的差异,只需要修改DOM树中不同的部分即可.也可以理解为Virtual DOM做了一个中间件,先利用JS修改Virtual DOM,在对比出差异后,将所有的更改加入页面的真实DOM.所以说,Virtual DOM的最大优势就在于完全不用像原生DOM,在对比之后还要进行DOM的重建与创造,因为这对于大型项目的运行来说非常消耗性能,开销极大.由此可见,不论在什么体量的网页中,放弃传统DOM采用Virtual DOM无疑是非常高效且绝佳的选择.

如何将DOM用virtual DOM 来表示

首先,在vscode中新建一个dom diff 项目,项目初始化,装好相应组件

由于是DOM树,所以在将HTML转换成DOM树时,要运用递归的形式,首先创建结点,其次设置属性,然后设置子节点

<ul class="list">
    <li class="item">wjy</li>
    <li class="item">易烊千玺</li>
</ul> 

// DOM 树的表达转换形式
let virtualDOM = createElement('ul', {
  class:'list',
}, [
  createElement('li',{
    class:'item'
  },['wjy']),
  createElement('li',{
    class:'item'
  },['易烊千玺']),
])

然后,在新建一个element.js文件进行向外输出,完成页面渲染

// 通过构造函数Element构造虚拟DOM节点
class Element {
    constructor(type,props,children){
        this.type = type;
        this.props = props;
        this.children =children;
    }
}
//
const createElement = (type,props,children) => {
  return new Element(type,props,children);
}

// 进行页面渲染 将Virtual Dom转化为真实DOM
const render = (domObj) => {
    let el = document.createElement(domObj.type);
    for(let key in domObj.props){
        setAttr(el,key,domObj.props[key]);
    }
    domObj.children.forEach(child => {
        child = (child instanceof Element)
        ? render(child)
        : document.createTextNode(child);
        el.appendChild(child);

    })
    return el;
}

function setAttr(node,key,value){
    switch(key){
        case 'value':
            if(node.tagName.toLowerCase() === 'input' ||
            node.tagName.toLowerCase() ==='textarea'
            ){
                node.value = value;
            }else{
                node.setAttribute(key,value)
            }
            break;
            case 'style':
            //   node.setAttribute('style',value)
            node.style.cssText = value;
            break;
            default:
                node.setAttribute(key,value)
            break;
    }
}

// 将真实DOM 挂载到制定根节点
const renderDOM = (el,target) => {
    target.appendChild(el);
}

// 向外输出
export {
    createElement,
    render,
    renderDOM
}

在控制台上得到真实DOM

页面渲染成功!😃

DOM DIFF算法

在用户进行操作更改交互页面操作后,虚拟DOM树上的节点会发生变化,然而此时真实节点却没有改变,为了使得更改与真实页面同步,我们会使用DOM DIFF算法找出这两颗树的差异,然后产生差异补丁对象,再将差异补丁对象应用到真实的DOM节点上去,于是完成了页面的渲染和更新。

传统的Diff算法时间复杂度达到了O(n^3),若要满足每次都可以整体刷新页面的目的,这种指数型的增长的性能开销是无法满足性能要求的,于是,Facebook的工程师对此进行了优化,通过制定diff策略将Diff算法的复杂度降低到了O(n)

Diff 策略

  • DOM节点跨层级的操作特别少,所以可以忽略不计
  • 拥有相同类的两个组件将会产生相似的树形结构,拥有不同类的两个组件将会产生不同的树形结构
  • 同一层级的一组子节点,他们可以通过uuid进行区分

Diff 粒度

由于DIFF的粒度不同,DIFF算法按照下面的顺序依次执行

  • Tree DIFF
  • Component DIFF
  • Element DIFF

打补丁

我们根据diff策略以及react diff中的比对算法将两个虚拟DOM通过深度优先遍历进行比较,如果有差异,就把所遍历到节点的索引值所对应的操作存储起来,也称为补丁对象(patches)

然后对真实的DOM再次经过深度优先遍历,补丁对象中的索引就会和DOM相对应,我们就完成了DOM的更新操作。

结语
在互联网环境下,随时刷新交互页面是我们上网的常规操作,然而这一简单的操作却是多次算法优化的结果。DOM DIFF的底层原理挺复杂,如果有感兴趣的朋友,可以自行搜索相关文献,因为本文只是浅析,所以太多方面就不赘述了,如果本文有知识错漏的地方,也欢迎指正!虚心接收一切合理批评!😉

如果这篇文章有帮助到你对dom diff算法的理解,也希望您能为我点一个赞😃,答主是刚入门的前端小白,每一个赞都是我前进的动力,我会持续更新掘金的博客的🤗

以上就是详解react应用中的DOM DIFF算法的详细内容,更多关于react应用的DOM DIFF算法的资料请关注我们其它相关文章!

(0)

相关推荐

  • React Diff原理深入分析

    在了解Diff前,先看下React的虚拟DOM的结构 这是html结构 <div id="father"> <p class="child">I am child p</p> <div class="child">I am child div</div> </div> 这是React渲染html时的js代码   自己可以在babel上试试 React.createElemen

  • react中的虚拟dom和diff算法详解

    虚拟DOM的作用 首先我们要知道虚拟dom的出现是为了解决什么问题的,他解决我们平时频繁的直接操作DOM效率低下的问题.那么为什么我们直接操作DOM效率会低下呢? 比如我们创建一个div,我们可以在控制台查看一下这个div上自带或者继承了很多属性,尤其是我们使用js操作DOM的时候,我们的DOM本身就很复杂,js的操作也会占用很多时间,但是我们控制不了DOM元素本身,因此虚拟DOM解决的是js操作DOM这一层面,其实解决的是减少了操作dom的次数 简单实现虚拟DOM 虚拟DOM,见名知意,就是假

  • React diff算法的实现示例

    前言 在上一篇文章,我们已经实现了React的组件功能,从功能的角度来说已经实现了React的核心功能了. 但是我们的实现方式有很大的问题:每次更新都重新渲染整个应用或者整个组件,DOM操作十分昂贵,这样性能损耗非常大. 为了减少DOM更新,我们需要找渲染前后真正变化的部分,只更新这一部分DOM.而对比变化,找出需要更新部分的算法我们称之为diff算法. 对比策略 在前面两篇文章后,我们实现了一个render方法,它能将虚拟DOM渲染成真正的DOM,我们现在就需要改进它,让它不要再傻乎乎地重新渲

  • react diff算法源码解析

    React中Diff算法又称为调和算法,对应函数名为reconcileChildren,它的主要作用是标记更新过程中那些元素发生了变化,这些变化包括新增.移动.删除.过程发生在beginWork阶段,只有非初次渲染才会Diff. 以前看过一些文章将Diff算法表述为两颗Fiber树的比较,这是不正确的,实际的Diff过程是一组现有的Fiber节点和新的由JSX生成的ReactElement的比较,然后生成新的Fiber节点的过程,这个过程中也会尝试复用现有Fiber节点. 节点Diff又分为两种

  • 浅谈从React渲染流程分析Diff算法

    React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法.这让我们可以无需担心性能问题而"毫无顾忌"的随时"刷新"整个页面,由虚拟DOM来确保只对界面上真正变化的部分进行实际的DOM操作.React在这一部分已经做到足够透明,在实际开发中我们基本无需关心虚拟DOM是如何运作的.然而,理解其运行机制不仅有助于更好的理解React组件的生命周期,而且对于进一步优化React程序也会有很大帮助. 1.什么是虚拟DOM 在React中,render执行的结果得到的

  • 详解react应用中的DOM DIFF算法

    前言 对我们搞前端的来说,目前最流行的两大前端框架毫无疑问当属React和Vue,对于这两大框架,想必大家也是再熟悉不过了.然而,这两大框架无一例外的全部放弃使用传统的DOM技术,却采用了以JS为基础的Virtual DOM技术,也可称作虚拟DOM.所以,到底什么是Virtual DOM?两大热门框架全部使用Virtual DOM的原因又是什么?接下来让我这个搞前端的人来好好地为您讲解一下DOM DIFF算法的牛逼之处. 什么是Virtual DOM? 如字面意思所说,Virtual DOM即

  • Vue3组件更新中的DOM diff算法示例详解

    目录 同步头部节点 同步尾部节点 添加新的节点 删除多余节点 处理未知子序列 移动子节点 建立索引图 更新和移除旧节点 移动和挂载新节点 最长递增子序列 总结 总结 在vue的组件更新过程中,新子节点数组相对于旧子节点数组的变化,无非是通过更新.删除.添加和移动节点来完成,而核心 diff 算法,就是在已知旧子节点的 DOM 结构.vnode 和新子节点的 vnode 情况下,以较低的成本完成子节点的更新为目的,求解生成新子节点 DOM 的系列操作. 举例来说,假说我们有一个如下的列表 <ul>

  • 详解React 16 中的异常处理

    详解React 16 中的异常处理 异常处理 在 React 15.x 及之前的版本中,组件内的异常有可能会影响到 React 的内部状态,进而导致下一轮渲染时出现未知错误.这些组件内的异常往往也是由应用代码本身抛出,在之前版本的 React 更多的是交托给了开发者处理,而没有提供较好地组件内优雅处理这些异常的方式.在 React 16.x 版本中,引入了所谓 Error Boundary 的概念,从而保证了发生在 UI 层的错误不会连锁导致整个应用程序崩溃:未被任何异常边界捕获的异常可能会导致

  • 详解React Native中如何使用自定义的引用路径

    目录 RN的相对路径地狱 RN的自定义路径需要的依赖 解决自定义引用路径导致的eslint报错问题 RN的相对路径地狱 我刚接触RN时,就发现所有的demo中给出来的路径都是相对路径,我自己的练习项目中想改成自定义的绝对路径,但是发现并没有我做前端时熟悉的webpack.config.js,所以也就不知道该怎么改了.伴随着RN的学习和开发练习,我的代码也变得越来越多,越来越复杂,我逐渐发现RN的相对路径越来越麻烦,比如我把某个文件移动到另一个不同深度的文件夹中,那么就需要把所有引用这个文件的地方

  • 详解Java分布式系统中一致性哈希算法

    业务场景 近年来B2C.O2O等商业概念的提出和移动端的发展,使得分布式系统流行了起来.分布式系统相对于单系统,解决了流量大.系统高可用和高容错等问题.功能强大也意味着实现起来需要更多技术的支持.例如系统访问层的负载均衡,缓存层的多实例主从复制备份,数据层的分库分表等. 我们以负载均衡为例,常见的负载均衡方法有很多,但是它们的优缺点也都很明显: 随机访问策略.系统随机访问,缺点:可能造成服务器负载压力不均衡,俗话讲就是撑的撑死,饿的饿死. 轮询策略.请求均匀分配,如果服务器有性能差异,则无法实现

  • 详解React项目中eslint使用百度风格

    1.安装百度Eslint Rule 插件 npm i -D eslint @babel/eslint-parser @babel/eslint-plugin @ecomfe/eslint-config // react项目 npm i -D eslint-plugin-react eslint-plugin-react-hooks // 如果需要支持typescript的话 npm i -D @typescript-eslint/parser @typescript-eslint/eslint-

  • 详解React开发中使用require.ensure()按需加载ES6组件

    首先介绍下动态加载函数: require.ensure([], (require)=>{ let A = require('./a.js').default; }) 如果想要动态加载出es6代码组件,直接require一个es6风格的组件是不行的,因为一般的语言编译工具(如babel),不支持直接require一个es6风格的组件. 那么有种办法可以解决:在es6方式书写的组件底部增加一句:module.exports = YouclassName; import React, {Compone

  • 详解React项目中碰到的IE问题

    最近接手一个React项目,在IE下碰到了俩问题 IE11报错如下: 跟踪一下之后,发现是一些其他的npm包里面用到了startsWith这个方法,可以自己polyfill一下: if (!String.prototype.startsWith) { String.prototype.startsWith = function (search, pos) { return this.substr(!pos || pos < 0 ? 0 : +pos, search.length) === sea

  • 详解为什么Vue中不要用index作为key(diff算法)

    前言 Vue 中的 key 是用来做什么的?为什么不推荐使用 index 作为 key?常常听说这样的问题,本篇文章带你从原理来一探究竟. 另外本文的结论对于性能的毁灭是针对列表子元素顺序会交换.或者子元素被删除的特殊情况,提前说明清楚,喷子绕道. 本篇已经收录在 Github 仓库,欢迎 Star: https://github.com/sl1673495/blogs/issues/39 示例 以这样一个列表为例: <ul> <li>1</li> <li>

  • 详解React中key的作用

    要了解React中key的作用,可以从key的取值入手,key的取值可以分为三种,不定值.索引值.确定且唯一值 在下面的代码中,key的取值是不定值(Math.random()) 问题: 点击按钮的时候,span的颜色会变成红色吗? import React, { useState } from 'react'; function App() { const [initMap, setInitMap] = useState([1,2,3,4]); const handleClick = () =

随机推荐