React虚拟DOM的理解
React虚拟DOM的理解
Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点可以将其称为VNode,用对象属性来描述节点,实际上它是一层对真实DOM的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说Virtual DOM就是一个Js对象,是更加轻量级的对DOM的描述,用以表示整个文档。
描述
在浏览器中构建页面时需要使用DOM节点描述整个文档。
<div class="root" name="root">
<p>1</p>
<div>11</div>
</div>
如果使用Js对象去描述上述的节点以及文档,那么便类似于下面的样子。
{
type: "tag",
tagName: "div",
attr: {
className: "root"
name: "root"
},
parent: null,
children: [{
type: "tag",
tagName: "p",
attr: {},
parent: {} /* 父节点的引用 */,
children: [{
type: "text",
tagName: "text",
parent: {} /* 父节点的引用 */,
content: "1"
}]
},{
type: "tag",
tagName: "div",
attr: {},
parent: {} /* 父节点的引用 */,
children: [{
type: "text",
tagName: "text",
parent: {} /* 父节点的引用 */,
content: "11"
}]
}]
}
React中的虚拟DOM
Virtual DOM是一种编程概念,在这个概念里,UI以一种理想化的,或者说虚拟的表现形式被保存于内存中,并通过如ReactDOM等类库使之与真实的DOM同步,这一过程叫做协调。这种方式赋予了React声明式的API,您告诉React希望让UI是什么状态,React就确保DOM匹配该状态,这样可以从属性操作、事件处理和手动DOM更新这些在构建应用程序时必要的操作中解放出来。
与其将Virtual DOM视为一种技术,不如说它是一种模式,人们提到它时经常是要表达不同的东西。在React的世界里,术语Virtual DOM通常与React元素关联在一起,因为它们都是代表了用户界面的对象,而React也使用一个名为fibers的内部对象来存放组件树的附加信息,上述二者也被认为是React中Virtual DOM 实现的一部分。
React中的虚拟DOM的历史
在之前,Facebook是PHP大户,所以React最开始的灵感就来自于PHP。
在2004年这个时候,大家都还在用PHP的字符串拼接来开发网站。
$str = "<ul>";
foreach ($talks as $talk) {
$str += "<li>" . $talk->name . "</li>";
}
$str += "</ul>";
这种方式代码写出来不好看不说,还容易造成XSS等安全问题。应对方法是对用户的任何输入都进行转义Escape,但是如果对字符串进行多次转义,那么反转义的次数也必须是相同的,否则会无法得到原内容,如果又不小心把HTML标签给转义了,那么HTML标签会直接显示给用户,从而导致很差的用户体验。
到了2010年,为了更加高效的编码,同时也避免转义HTML标签的错误,Facebook开发了XHP。XHP是对PHP的语法拓展,它允许开发者直接在PHP中使用HTML标签,而不再使用字符串。
$content = <ul />;
foreach ($talks as $talk) {
$content->appendChild(<li>{$talk->name}</li>);
}
这样的话,所有HTML标签都使用不同于PHP的语法,我们可以轻易的分辨哪些需要转义哪些不需要转义。不久的后来,Facebook的工程师又发现他们还可以创建自定义标签,而且通过组合自定义标签有助于构建大型应用。
到了2013年,前端工程师Jordan Walke向他的经理提出了一个大胆的想法:把XHP的拓展功能迁移到Js中,首要任务是需要一个拓展来让JS支持XML语法,该拓展称为JSX。因为当时由于Node.js在Facebook已经有很多实践,所以很快就实现了JSX。
const content = (
<TalkList>
{talks.map(talk => <Talk talk={talk} />)}
</TalkList>
);
在这个时候,就有另外一个很棘手的问题,那就是在进行更新的时候,需要去操作DOM,传统 DOM API细节太多,操作复杂,所以就很容易出现Bug,而且代码难以维护。然后就想到了PHP时代的更新机制,每当有数据改变时,只需要跳到一个由PHP全新渲染的新页面即可。
从开发者的角度来看的话,这种方式开发应用是非常简单的,因为它不需要担心变更,且界面上用户数据改变时所有内容都是同步的。为此React提出了一个新的思想,即始终整体刷新页面,当发生前后状态变化时,React会自动更新UI,让我们从复杂的UI操作中解放出来,使我们只需关于状态以及最终UI长什么样。这个时候,我只需要关系我的状态(数据是什么),以及UI长什么样(布局),不再需要关系操作细节。
这种方式虽然简单粗暴,但是很明显的缺点,就是很慢。另外还有一个问题就是这样无法包含节点的状态,比如它会失去当前聚焦的元素和光标,以及文本选择和页面滚动位置,这些都是页面的当前状态。
为了解决上面说的问题,对于没有改变的DOM节点,让它保持原样不动,仅仅创建并替换变更过的DOM节点,这种方式实现了DOM节点复用Reuse。至此,只要能够识别出哪些节点改变了,那么就可以实现对DOM的更新,于是问题就转化为如何比对两个DOM的差异。说到对比差异,可能很容易想到版本控制git。DOM是树形结构,所以diff算法必须是针对树形结构的,目前已知的完整树形结构的编辑距离diff算法复杂度为O(n^3)。但是时间复杂度O(n^3)太高了,所以Facebook工程师考虑到组件的特殊情况,进行了一些优化与折中,然后将复杂度降低到了O(n)。
DOM是复杂的,对它的操作尤其是查询和创建是非常慢非常耗费资源的。看下面的例子,仅创建一个空白的div,其实例属性就达到294个。
// Chrome v84
const div = document.createElement("div");
let m = 0;
for (let k in div) m++;
console.log(m); // 294
对于DOM这么多属性,其实大部分属性对于做Diff是没有任何用处的,所以如果用更轻量级的Js对象来代替复杂的DOM节点,然后把对DOM的diff操作转移到Js对象,就可以避免大量对DOM的查询操作。这个更轻量级的Js对象就称为Virtual DOM。那么现在的过程就是这样:
- 维护一个使用
Js对象表示的Virtual DOM,与真实DOM一一对应。 - 对前后两个
Virtual DOM做diff,生成变更Mutation。 - 把变更应用于真实
DOM,生成最新的真实DOM。
可以看出,因为要把变更应用到真实DOM上,所以还是避免不了要直接操作DOM,但是React的diff算法会把DOM改动次数降到最低。关于React中的虚拟DOM创建过程可以参考https://github.com/facebook/react/blob/9198a5cec0936a21a5ba194a22fcbac03eba5d1d/packages/react/src/ReactElement.js#L348。
总结
传统前端的编程方式是命令式的,直接操纵DOM,告诉浏览器该怎么干,这样的问题就是,大量的代码被用于操作DOM元素,且代码可读性差,可维护性低。React的出现,将命令式变成了声明式,摒弃了直接操作DOM的细节,只关注数据的变动,DOM操作由框架来完成,从而大幅度提升了代码的可读性和可维护性。
在初期我们可以看到,数据的变动导致整个页面的刷新,这种效率很低,因为可能是局部的数据变化,但是要刷新整个页面,造成了不必要的开销。所以就有了Diff过程,将数据变动前后的DOM结构先进行比较,找出两者的不同处,然后再对不同之处进行更新渲染。但是由于整个DOM结构又太大,所以采用了更轻量级的对DOM的描述—虚拟DOM。
不过需要注意的是,虚拟DOM和Diff算法的出现是为了解决由命令式编程转变为声明式编程、数据驱动后所带来的性能问题的。换句话说,直接操作DOM的性能并不会低于虚拟DOM和Diff算法,甚至还会优于。框架的意义在于为你掩盖底层的DOM操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护,没有任何框架可以比纯手动的优化DOM操作更快,因为框架的DOM操作层需要应对任何上层API可能产生的操作,它的实现必须是普适的。
虚拟DOM优缺点
优点
Virtual DOM在牺牲(牺牲很关键)部分性能的前提下,增加了可维护性,这也是很多框架的通性。- 实现了对
DOM的集中化操作,在数据改变时先对虚拟DOM进行修改,再反映到真实的DOM,用最小的代价来更新DOM,提高效率。 - 打开了函数式
UI编程的大门。 - 可以渲染到
DOM以外的端,使得框架跨平台,比如ReactNative,React VR等。 - 可以更好的实现
SSR,同构渲染等。 - 组件的高度抽象化。
缺点
- 首次渲染大量
DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。 - 虚拟
DOM需要在内存中的维护一份DOM的副本,多占用了部分内存。 - 如果虚拟
DOM大量更改,这是合适的。但是单一的、频繁的更新的话,虚拟DOM将会花费更多的时间处理计算的工作。所以如果你有一个DOM节点相对较少页面,用虚拟DOM,它实际上有可能会更慢,但对于大多数单页面应用,这应该都会更快。
每日一题
https://github.com/WindrunnerMax/EveryDay
参考
https://zhuanlan.zhihu.com/p/99973075
https://www.jianshu.com/p/e0a3ac85db5c
https://www.jianshu.com/p/9a1d2750457f
https://github.com/livoras/blog/issues/13
https://juejin.cn/post/6844904165026562056
https://juejin.cn/post/6844903640512086029
https://zh-hans.reactjs.org/docs/faq-internals.html#what-is-the-virtual-dom
React虚拟DOM的理解的更多相关文章
- React虚拟DOM浅析
在Web开发中,需要将数据的变化实时反映到UI上,这时就需要对DOM进行操作,但是复杂或频繁的DOM操作通常是性能瓶颈产生的原因,为此,React引入了虚拟DOM(Virtual DOM)的机制. 什 ...
- React虚拟DOM具体实现——利用节点json描述还原dom结构
前两天,帮朋友解决一个问题: ajax请求得到的数据,是一个对象数组,每个对象中,具有三个属性,parentId,id,name,然后根据这个数据生成对应的结构. 刚好最近在看React,并且了解到其 ...
- react虚拟dom diff算法
react虚拟dom:依据diff算法 前端:更新状态.更新视图:所以前端页面的性能问题主要是由Dom操作引起的,解放Dom操作复杂性 刻不容缓 因为:Dom渲染慢,而JS解析编译相对非常非常非常快! ...
- 深入理解React虚拟DOM
一.什么是虚拟DOM 虚拟DOM可以看做一棵模拟了DOM树的JavaScript对象树.比如: var element = { element: 'ul', props: { id:"uli ...
- React 虚拟 DOM 的差异检测机制
React 使用虚拟 DOM 将计算好之后的更新发送到真实的 DOM 树上,减少了频繁操作真实 DOM 的时间消耗,但将成本转移到了 JavaScript 中,因为要计算新旧 DOM 树的差异嘛.所以 ...
- Virtual DOM 虚拟DOM的理解(转)
作者:戴嘉华 转载请注明出处并保留原文链接( #13 )和作者信息. 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM ...
- 关于react虚拟DOM的研究
1.传统的前端是这样的,我在学校也都是这样做的,html(jsp)主要负责提供所有的DOM节点,而javascript负责动态效果,比如按钮点击,图片轮播等,这样的话javascript如何组织结构是 ...
- react系列一,react虚拟dom如何转成真实的dom
react,想必作为前端开发一定不陌生,组件化以及虚拟dom使得react成为最受欢迎额前端框架之一.我们知道react是基于虚拟dom的,但是什么是虚拟dom呢,其实就是一组js对象,那么我们今天就 ...
- 浅谈React虚拟DOM
为什么要使用虚拟DOM 因为浏览器的DOM渲染是非常消耗性能的,很低效,我们使用虚拟DOM是为了提高DOM的渲染性能: 什么是虚拟DOM 虚拟DOM就是把真实的DOM树通过createElement转 ...
- React/虚拟DOM
在说虚拟DOM之前,先来一个引子,从输入url到展现出整个页面都有哪些过程? 1.输入网址 2.DNS解析 3.建立tcp连接 4.客户端发送HTPP请求 5.服务器处理请求 6.服务器响应请求 7. ...
随机推荐
- [转帖]Redis优化:Redis使用TCMalloc提高内存分配性能
TCMalloc(Thread-Caching Malloc)是google开发的开源工具──"google-perftools"中的成员.与标准的glibc库的malloc相比, ...
- Grafana监控Oracle数据库的表大小等信息
Grafana监控Oracle数据库的表大小等信息 方案 oracledb_exporter 以及 prometheus grafana 使用的SQL以及配置文件 [[metric]] context ...
- [转帖]ss 输出格式说明
ss 命令输出详解ss 全名socket statistics,是iproute2中的一员ss已经替代netstat,大热于江湖.但是关于ss命令输出的内容,是什么意思呢? [root@test]# ...
- [转帖]调优"四剑客"的实战演练,福尔摩斯•K带你轻松优化性能
前言 天下武功,唯快不破.在侦探的世界中,破案效率永远是衡量一名侦探能力的不二法门.作为推理界冉冉升起的新星,大侦探福尔摩斯·K凭借着冷静的头脑.严谨的思维,为我们展现了一场场华丽而热血的推理盛宴. ...
- 【转帖】浅析经典JVM垃圾收集器-Serial/ParNew/Parallel Scavenge/Serial Old/Parallel Old/CMS/G1
https://zhuanlan.zhihu.com/p/481256418 在讲述垃圾收集器之前,我们得先知道JVM中常见的垃圾收集算法有什么,具体请参考我的这篇博文.如果说收集算法是内存回收的方法 ...
- [转帖]Linux文件夹对比并提取的差分文件技巧-rsync的妙用
https://www.xitongjiaocheng.com/linux/2017/45720.html 需求 最近团队正在开发一个版本对比工具,要求是把A1文件夹与A2对比,将A2中的增量部分 ...
- [转帖]nmon使用及监控数据分析
[使用] [监控数据分析] 参考链接:nmon监控数据分析 性能测试中,各个服务器资源占用统计分析是一个很重要的组成部分,通常我们使用nmon这个工具来进行监控以及监控结果输出. 一.在监控阶段使用类 ...
- [转贴]汉字编码:GB2312, GBK, GB18030, Big5
汉字编码:GB2312, GBK, GB18030, Big5 https://www.cnblogs.com/malecrab/p/5300497.html 前一篇博文:ANSI是什么编码?中有这样 ...
- 京音平台-一起玩转SCRM之电销系统
作者:京东科技 李良文 一.前言 电销是什么?就是坐席拿着电话给客户打电话吗?no no no,让我们一起走进京音平台之电销系统. 京音平台2020年初开始建设,过去的两年多的时间里,经历了跌宕起伏, ...
- vue插件实现表格拖拽 sortable 遇见的坑
下载插件 npm install sortable.js --save (下载的时候一定要这样去下载,不要去下载 npm install sortable--save ) 因为sortable.js和 ...