虚拟 DOM 和 DOM diff 简介 ★

简介: 虚拟 DOM 和 DOM diff 简介 ★

1、虚拟 DOM 是个啥?

虚拟 DOM 和 DOM diff 这两个概念在 Vue 和  React 中经常被提到,也是作为面试时的一个区分初级和高级前端的知识点,这两个概念可以说是非常重要了,但是其实只要你仔细研究一下就会发现它实际上并没有什么难度!


虚拟 DOM 与真实 DOM 相对应,它其实就是通过 JavaScript 对象来模拟真实的 DOM 树。只不过这个 JavaScript 对象比较特殊,它内部通常含有标签名、标签上的属性、事件监听和子元素,以及一些其他属性。


为什么要用虚拟 DOM 来模拟真实的 DOM 树,这样难道不像是脱裤子放屁一样吗,直接用真实 DOM 不好吗?


作为前端开发人员都知道一个名为 jQuery 的库,据统计,全世界排名前100万的网站,有 46% 使用 jQuery,远远超过其他库,感兴趣的同学可以看一下我的这篇博客:作为一个前端新人,还要不要学 jQuery,但是最近的这几年 jQuery 仿佛开始慢慢退出历史的舞台,渐渐的被现如今流行的 Vue、React、Angular 三大框架所代替。


其实通过上面的这个事情,我们就可以发现一些蛛丝马迹,为什么会出现这种现象,一定是 jQuery 存在某些方面的缺陷。


其实有一部分原因其实就是 jQuery 一直是基于原生 JavaScript API 封装的一个直接操作真实 DOM 的库,而如今流行的 Vue、React、Angular 三大框架是基于虚拟 DOM 的,为什么基于真实 DOM 思想的库慢慢被遗忘,基于虚拟 DOM 的框架越来越被开发人员所追捧,虚拟 DOM 到底是有什么魔法?

2、虚拟 DOM 的优点?

不知道大家有没有听说过 "DOM 操作慢,虚拟 DOM 快!" 这样的言论?这句言论的出处应该是 2010 年出版的一本高性能的 JavaScript 书中的观点,但是书上却并没有给出任何数据来证实这一点。


就个人观点而言,感觉 "DOM 操作慢,虚拟 DOM 快!" 这句话不够严谨,就比如有人说宇宙飞船快,确实快,但是和光速相比呢?DOM 操作慢是对比于 JS 原生 APl 来说的,如 DOM 操纵确实比数组操作要慢,不过任何基于 DOM 的库(Vue/React)都不可能在操作 DOM 时比 DOM 更快。


那为什么网上还是会有 "虚拟 DOM 快" 的言论呢?这是因为在某些情况下,虚拟 DOM 确实要更快。


优点一:虚拟 DOM 可以减少 DOM 操作


虚拟 DOM 可以将多次操作合并为一次操作:比如需要向页面中添加 1000 个节点,基于真实 DOM 的原生 JavaScript 是一个接一个操作,将 1000 个节点插入到页面上。而虚拟 DOM 则是会先用 JavaScript 对象模拟 DOM 树,先在该对象上执行 1000 次添加节点的操作,然后将这个虚拟 DOM 一次性的插入到页面上。


虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加 1000 个节点,其实页面上已经存在了前 9990 个节点了,只有 10 个是新增的,基于真实 DOM 的原生 JavaScript 会先将原来的 9990 个节点删掉,然后再将这 10000 个节点添加到页面上。而虚拟 DOM 可以借助 DOM diff 算法,对比两棵 DOM 树的不同点,得到最小更新的 patch(就是那 10 个节点),只将这 10 个新节点插入到页面上即可。


优点二:虚拟 DOM 的跨平台性


虚拟 DOM 不仅可以变成真实 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象。

3、虚拟 DOM 的真面目?

说完虚拟 DOM 的优点,我们来看一下这个虚拟 DOM 的真面目。文章开头也说了,虚拟 DOM 其实就是一个 JavaScript 对象,在 Vue 和 React 中可能会稍有不同,下面带大家分别来看一下:


首先是 React 的虚拟 DOM,在 React 中,使用 React.createElement() 来创建虚拟 DOM,具体如下:

const VNode = {    /*React 中的虚拟 DOM*/      React.createElement('div',{className:'red',onClick:()=>{}}, [
    key: null,                                    React.createElement('span',{},'span1'),
    props: {                                      React.createElement('span',{},'span1'),
        children: [    /*子元素s*/             ])    /*React 创建虚拟 DOM 的步骤*/
            {type:'span', ...},               //这种写法比较麻烦,React支持jsx的写法
            {type:'span', ...}                //原理是通过babel将jsx转义为上述这种写法
        ],
        className: "red"    //标签上的属性
        onClick: () => {}    //事件
    },
    ref: null,
    type: "div", //标签名or组件名
    ...
}

然后是 Vue 的虚拟 DOM,在 Vue 中只能在 render 函数中使用 h() 来创建虚拟 DOM,具体如下:

const VNode = {      /*Vue 中的虚拟 DOM*/        
    tag: "div",      /*标签名或组件名*/             
    data: {
        class: "red", /*标签上的属性*/                  
        on:{ click: ()=>{} /*事件*/}
    },
    children: [    //子元素s
        {tag:"span", ...},
        {tag:"span", ...}
    ],
    ...
}
h('div', { class: 'red',on: {click: () => {}} },    /*创建虚拟 DOM 的过程*/
    [h('span', {}, 'span1'),h('span', {}, 'span2')] //这种写法写法比较麻烦,Vue 支持template写法
)                                                   //原理是通过compiler或vue-loader转义成上述这种h函数的写法

以上就是 Vue 和 React 创建虚拟 DOM 的过程了,你可能会发现:上述创建虚拟 DOM 的方式特别麻烦,长得丑且对开发人员特别不友好,这是虚拟 DOM 的第一个缺点。为了解决这个问题,React 和 Vue 分别推出了自己的解决方式。


React 通过 babel 转义,支持开发人员书写形如 html in js 的代码,这些代码最终会被 babel 转义为真的虚拟 DOM,而 Vue 则是走 vue-loader,将 Vue 中的 template 中写的 html 代码加载为真的虚拟 DOM。但是这样一来虚拟 DOM 的转义又非常依赖打包工具,这是虚拟 DOM 的第二个缺点。


React 和 Vue 两者在使用打包工具后的虚拟 DOM 写法可能和真实的 html 存在一定的差异,这里就不详细赘述了。

4、DOM diff 又是个啥?

DOM diff 其实就是虚拟 DOM 的对比算法,该算法主要是对比两棵虚拟 DOM 的不同点。在 Vue 和 React 中,两者的共同点是在 DOM diff 时都是:"只进行同级节点的比较,忽略跨级节点"。当然二者也有不同点,比如 Vue 内含有双指针对比,这里先不做解释,感兴趣的小伙伴可以自行研究。


这里通过例子来描述一下 DOM diff 的全过程,比如下面的虚拟 DOM,通过控制变量 visible 的值来动态的向 div 中添加和删除该节点,在添加和删除的过程中,就会有 DOM diff 算法的过程:

<!--visible 为 true-->                          <!--visible 为 false-->
<div :class="color">                            <div :class="color">
    <span v-if="visible">hello</span>               <span v-if="visible">hello</span>
    <span>world</span>                              <span>world</span>      
</div>                                          </div>

当 visible 变量从 true 变为 false 时,DOM diff 算法会发现,div 没变,不需要更新。第一个子元素的标签没变,但是内容发生变化了,更新 DOM。第二个子元素没了,删除其对应的 DOM。


其实这和我们常人的想想还是有所不同的,经过上面的 DOM diff 后,现在 div 内剩下的是一个内容为 hello 的 span 元素。但是这个 span 元素却并不是一开始的 hello 的 span,而是 world 改为的 hello。


上述 diff 过程其实是存在 bug 的,既然是删除第一个 span,为什么不直接删除呢?记住这个点,这个问题可以优化!


总结一下:DOM diff 其实就是一个函数,我们通常称之为 patch,这个函数接收两个虚拟 DOM 对象,然后通过函数内部的逻辑,找出两棵虚拟 DOM 的区别:patches = patch(oldVNode, newVNode) ,而这个 patches 就是需要更新的 DOM 操作。


对比过程中大概分为以下几种逻辑:


Tree diff:将新旧两棵树逐层对比,找出哪些节点需要更新。如果节点是组件就走 Component diff,否则就走 Element diff

Component diff:节点是组件,类型不同直接替换(删旧),类型相同则只更新属性,然后深入组件做 Tree diff(递归)

Element diff:节点是原生标签,看标签名,标签名不同直接替换,相同则只更新属性,后进入标签后代做 Tree diff(递归)

5、DOM diff 中 key 的作用?

刚才说 DOM diff 是存在 bug 的,既然是删除第一个 span,为什么不直接删除呢?反而还浪费第二次 diff 的性能。


有问题就会有解决方式,Vue 和 React 都建议在执行列表渲染时给每个组件添加上 key 这个属性,这个 key 属性就是节点的唯一标识,有了这个标识再看刚才的例子,DOM diff 时发现第一个 span 的 key 不见了,就直接删掉第一个 span 元素即可。


有了这个 key,就会使 DOM diff 的过程变得更加高效,但是在使用的时候,这个 key 也是有注意点的: key 的值必须使一个唯一的且不可变的值,否则在某些情况下会出问题的。


至于什么问题,感兴趣的小伙伴可以参考一下我的这篇博客:Vue - v-for 中为什么不能用 index 作为 key


先说结论:在列表渲染时,key 值的绑定对象最好不要使用 index。


为什么不能用 index 作为 key,如果你用 index 作为 key,那么在删除第二项的时候,index 就会从 1 2 3 变成 1 2(而不是 1 3),那么 Vue 依然会认为你删除的是第三项。也就是会遇到上面一样的 bug。


所以,永远不要用 index 作为 key。永远不要!除非你是大神。能清楚地知道如何解决 index做 key 带来的 bug。有人说简单的场景可以用 key。问题在于,你如何确保需求会一直保持简单?只要出现了删除一项或新增一项的需求,而且这一项里面含有子组件,上面说的 bug 就有可能出现。

目录
相关文章
|
3月前
|
JavaScript 前端开发 算法
React技术栈-虚拟DOM和DOM diff算法
这篇文章介绍了React技术栈中的虚拟DOM和DOM diff算法,并通过一个实际案例展示了如何使用React组件和状态管理来实现动态更新UI。
46 2
|
4月前
|
JavaScript 前端开发 算法
react中虚拟dom和diff算法
在React中,虚拟DOM(Virtual DOM)和Diff算法是两个核心概念,它们共同工作以提高应用的性能和效率。
46 4
|
2月前
|
JavaScript 前端开发 算法
真实DOM和虚拟DOM有哪些区别?
本文介绍了真实DOM和虚拟DOM的概念、使用方式、优势、劣势、使用场景、影响因素、开发效率和性能对比。真实DOM是浏览器提供的原生接口,直接操作简单直观,但频繁操作会导致性能损耗。虚拟DOM是真实DOM的抽象表示,通过比较差异减少DOM操作,适用于大规模数据变更和复杂交互的页面。开发者应根据具体需求选择合适的DOM操作方式,以提高页面性能和开发效率。
103 1
真实DOM和虚拟DOM有哪些区别?
|
3月前
|
XML JavaScript 前端开发
学习react基础(1)_虚拟dom、diff算法、函数和class创建组件
本文介绍了React的核心概念,包括虚拟DOM、Diff算法以及如何通过函数和类创建React组件。
36 3
|
4月前
|
移动开发 JavaScript 前端开发
【Vue面试题二十二】、什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路
这篇文章深入探讨了虚拟DOM的概念、必要性以及在Vue中的实现方式,解释了虚拟DOM如何作为真实DOM的轻量级抽象,通过优化DOM操作提高性能,并实现跨平台渲染的能力。
【Vue面试题二十二】、什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路
|
3月前
|
机器学习/深度学习 JavaScript 算法
面试中的网红虚拟DOM,你知多少呢?深入解读diff算法
该文章深入探讨了虚拟DOM的概念及其diff算法,解释了虚拟DOM如何最小化实际DOM的更新,以此提升web应用的性能,并详细分析了diff算法的实现机制。
|
4月前
|
JavaScript 算法 前端开发
"揭秘虚拟DOM的神奇魔法:从零开始打造高效渲染引擎,颠覆你的DOM操作认知!"
【8月更文挑战第20天】虚拟DOM是一种优化技术,通过在内存中构建DOM树的轻量级副本,减少浏览器重排和重绘,提升性能。本文简要介绍了DOM及其重要性,并深入解释了虚拟DOM的概念。虚拟DOM通过模拟真实DOM结构,在内存中进行数据更新,仅将变动部分同步到实际DOM。文中还提供了一个简易虚拟DOM的实现方案,包括虚拟节点创建、渲染函数及一个基本的diff算法,用于比对新旧虚拟DOM并高效更新实际DOM。通过示例展示了如何构建和渲染一个简单的虚拟DOM。这有助于理解虚拟DOM的基本工作原理和技术细节。
61 4
|
4月前
|
JavaScript 前端开发 API
区分 DOM 与虚拟 DOM
【8月更文挑战第24天】
126 0
|
6月前
|
JavaScript 前端开发 算法
虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。
【6月更文挑战第27天】虚拟DOM是React的关键技术,它是个轻量的JS对象树,模拟实际DOM结构。当状态改变,React不直接修改DOM,而是先构建新的虚拟DOM树。通过 diff 算法比较新旧树,找到最小变更,仅更新必要部分,提高性能,避免频繁DOM操作。虚拟DOM还支持跨平台应用,如React Native。它优化了更新流程,简化开发,并提升了用户体验。
47 1
|
6月前
|
JavaScript 前端开发 API
jQuery对象与DOM对象简介及相互转换
jQuery对象与DOM对象简介及相互转换