前端框架中那些或惊艳或坑爹的设计——React篇之Virtual DOM

前端有非常多的框架,React,Vue,Angular,Solid等,每个框架都有其独特的设计,这些设计或者惊艳,让人赞叹,或者坑爹,被人吐槽,但是无论如何这些设计都有其诞生的原因和背景,去看看这些设计的诞生,无论好坏,我们都有可以借鉴的地方,或许我们可以博采众长,创造一个汇集了所有优点的框架。

这是这个系列的第一篇文章。

什么是Virtual DOM

Virtual DOM,也就是虚拟DOM,React官网给出的定义是:

虚拟 DOM (VDOM) 是一个编程概念,在这个概念里,UI 以一种理想化的,或者说虚拟的表现形式被保存于内存中,并通过 ReactDOM 等库与"真实"DOM 同步。

更通俗一点,Virtual DOM,就是使用 JavaScript 对象来具体表示 DOM 信息和结构。

比如我们有这样一个HTML片段:

ini 复制代码
<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ul>

浏览器会为这个HTML生成一颗DOM树,我们可以使用提供的方法,来操作这棵DOM树,比如增加一个节点

javascript 复制代码
let li = document.createElement("li");
li.setAttribute('class','item');
li.append('Item 4');
document.getElementById('list').append(li);

当我们需要增加10个节点的时候,我们就需要执行上面的逻辑10遍

而如果我们将这段HTML用JS来表述,比如用下面的结构

css 复制代码
var element = {
  tagName: 'ul',
  props: {
    id: 'list'
  },
  children: [ 
    {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
  ]
}

那么我们增加一个节点,只需要添加一个children

css 复制代码
var element = {
  tagName: 'ul',
  props: {
    id: 'list'
  },
  children: [ 
    {tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
    {tagName: 'li', props: {class: 'item'}, children: ["Item 4"]},

  ]
}

当我们短时间有多次操作时,我们就可以在这个JS中先把操作都做完,然后再根据变动的内容,再统一去改动真实的DOM,这样能有效减少浏览器的回流和重绘,以最少的代价渲染 DOM。

所以Virtual DOM 的本质是一个用来映射真实 DOM 的 JavaScript 对象,在用户和真实DOM之间架了一座桥,方便用户来操作DOM。

同时,也要明确Virtual DOM是一个概念,不是一种实现,所以同样是使用Virtual DOM,React和Vue的实现并不完全一致,当然,你也可以自己实现一个Virtual DOM的实现。

为什么会有Virtual DOM

在没有Virtual DOM之前,难道我们就无法操作DOM了吗,当然不是,无论浏览器还是jquery这类的库都为我们提供了比较方便的操作DOM的方法,那么我们为什么还需要Virtual DOM来帮我们操作DOM呢?

这就要回到提出Virtual DOM这个概念的React团队来了。

早在2010年,React诞生之前,Facebook (现在的meta)开发了 XHP 。XHP 是对 PHP 的语法拓展,它允许开发者直接在 PHP 中使用 HTML 标签,而不再使用字符串,并且允许自定义标签,使用起来就像这样

php 复制代码
$list = <ul />;
$items = ...;

foreach ($items as $item) {
  $list->appendChild(<li>{$item}</li>);
}

到了2013 年,Facebook (现在的meta)前端工程师 Jordan Walke 开始捣鼓React,受到XHP的启发,他想把把 XHP 的拓展功能迁移到 JS 中,也就是现在的JSX。

然而问题来了,操作DOM是个较为复杂的操作,尤其当你的DOM的结构非常复杂是,一旦你的DOM的结构进行变化,我操作DOM的逻辑也要更着变化,这块的维护过于复杂了,对于一些复杂的页面,我可能需要非常多的DOM操作,逻辑又多又杂,还容易出错。

为此 React 提出了一个新的思想,即始终整体"刷新"页面,不关心谁发生了变化,当发生前后状态变化时,React 会自动更新全部的UI,这让我们从复杂的 UI 操作中解放出来,使我们只需关于状态以及最终 UI 长什么样,这就是声明式编程。

当然显然这个是不太适合用于生产环境的,我就改一个文案,整个页面都刷新UI,虽然无脑的一波梭哈很爽,但是显然很慢,且完全没有必要。

为了解决上面说的问题,我们最好能做到增量更新,对于没有改变的 DOM 节点,让它保持原样不动,仅仅创建并替换变更过的 DOM 节点。所以,问题就来到了,两段JSX,我们怎么找出变更的节点。

第一个想法自然是对比DOM树了,然而DOM树在我们更新完DOM的时候才会变更,我们无法提前知道这个DOM树会变成什么样,那现成的DOM树无法使用,那就只能自己搞一个虚拟DOM树了,这个虚拟DOM树还比原生的DOM树简单,更能自定义,完美。

于是,在React中,渲染更新的过程变成了这样

  1. 维护一个使用 JS 对象表示的 Virtual DOM,与真实 DOM 一一对应
  2. 对前后两个 Virtual DOM 做 diff ,生成变更(Mutation)
  3. 把变更应用于真实 DOM,生成最新的真实 DOM

简单的说,Virtual DOM的出现,本身就是为了适应React这种声明式 UI框架,对于以前我们直接使用js操作DOM来讲,Virtual DOM没有什么意义,但是像React这种摒弃了直接操作 DOM 的细节,只关注数据的变动,数据再映射成UI的框架来讲,Virtual DOM能尽可能地减少真实DOM的操作。

React这类声明式框架,大幅度提升了代码的可读性和可维护性。而Virtual DOM和diff算法,则大幅度提升了React等框架的渲染和更新性能。

这也就是为什么你基本看到Virtual DOM都是和React,Vue框架绑定在一起的。

Virtual DOM的优点

很多文章和面试回答,都会告诉你直接操作DOM慢,使用Virtual DOM快,也有很多文章驳斥了这种观点,比如下面的文章:

我们要理解,Virtual DOM最终还是会映射成真实的DOM的操作,说白了,最终和你直接操作DOM没有任何区别,只不过一个是你手动编码操作,一个是框架自动diff并操作DOM,还多了Virtual DOM的构建和diff的操作,显而易见会比直接操作DOM慢。

我们认为Virtual DOM的快是针对UI全局刷新的方式来说的。直接操作DOM确实快,但是难以维护,编码的心智负担较大,使用React但是没有Virtual DOM,维护UI的心智负担小了,但是全局刷新造成的性能消耗大,React加上Virtual DOM,在保证减少开发人员维护UI的心智负担的同时以最小的代价来进行更新 DOM。

当然,Virtual DOM带来的好处也是显而易见的

  • 最根本的一点,抽象化了DOM,所以可以基于这个抽象做很多事情,比如跨平台的能力,ReactNative,React VR 等
  • 基于Virtual DOM,可以实现了对 DOM 的集中化操作,在数据改变时先对虚拟 DOM 进行修改,再反映到真实的 DOM 中,用最小的代价来更新 DOM,提高效率,解决了react这类框架数据驱动后所带来的性能问题的。

Virtual DOM的缺点

至于Virtual DOM的缺点,其实就是慢,毕竟是牺牲了部分性能换来的

  • 渲染 DOM 时,由于多了一层虚拟 DOM 的计算,会比直接调用插入慢。
  • 由于引入了额外的一层虚拟 DOM ,所以实际的内存占用也大了

当然相比Virtual DOM带来的性能和空间上的损耗,其优势会更明显一点。

可以借鉴的

自React 捣鼓出Virtual DOM以来,其实其他框架也在借鉴,比如Vue,引用尤大的话来说

Vue 2.0 引入 vdom 的主要原因是 vdom 把渲染过程抽象化了,从而使得组件的抽象能力也得到提升,并且可以适配 DOM 以外的渲染目标。

我们可以从Virtual DOM的诞生中得到一点启示

  • 抽象化

将复杂的,不好描述的进行合理的抽象,结构化,基于抽象化,可以扩展得更多

  • 取舍与权衡

所有框架,所有库不可能尽善尽美,一旦引入了框架,势必会比直接调用底层API多一点小号,但是这不代表不好,有时候我们需要做一点权衡,去牺牲一点其他相对不重要的,比提升一些更重要的,比如牺牲一部分性能,获取更好的开发体验。

相关推荐
下雪天的夏风5 分钟前
TS - tsconfig.json 和 tsconfig.node.json 的关系,如何在TS 中使用 JS 不报错
前端·javascript·typescript
diygwcom17 分钟前
electron-updater实现electron全量版本更新
前端·javascript·electron
volodyan20 分钟前
electron react离线使用monaco-editor
javascript·react.js·electron
^^为欢几何^^28 分钟前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
Hello-Mr.Wang33 分钟前
vue3中开发引导页的方法
开发语言·前端·javascript
程序员凡尘1 小时前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七4 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
北岛寒沫5 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy5 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
(⊙o⊙)~哦6 小时前
JavaScript substring() 方法
前端