React框架的核心优势之一, 就是支持创建虚拟DOM来提高页面性能。那么, 什么是虚拟DOM呢?其实, 虚拟DOM这个概念很早就被提出来了, 是相对于实际DOM而言的。
设计人员在设计传统HTML网页的UI时, 都会在页面中定义若干的DOM元素, 这些DOM元素是所谓的实际DOM。通常, 页面中的实际DOM负责承载着外观表现和数据变化,任何外观形式的改变或数据信息的更新都要反馈到UI上, 都是需要通过操作实际DOM来实现的。
于是, 问题也就自然出现了。对于复杂的页面UI而言, 往往会定义大量的实际DOM。频繁地操作大量实际DOM, 往往会带来访问性能的严重下降, 用户体验也会随之变差, 这些都是设计人员所不希望看到的。因此, React框架专门针对这个现象引入了虚拟DOM机制,以避免频繁的DOM操作带来的性能下降问题。
React DOM类似于一种将相关的实际DOM组合在一起的集合, 是有区别于传统概念上的DOM元素的,如果将其理解为DOM组件应该更为贴切。因此,React框架将ReactDOM称为虚拟DOM
实际DOM的构建
<body>
<div id="id-div">
</div>
<script type="text/javascript">
//定位到div
const divDom = document.getElementById('id-div');
//创建DOM控件
const jsSpan = document.createElement('span');
const jsH3 = document.createElement('h3');
jsH3.innerText = '实际的DOM';
const jsP = document.createElement('p');
jsP.innerText = 'DOM1';
jsSpan.appendChild(jsH3);
jsSpan.appendChild(jsP);
divDom.appendChild(jsSpan)
</script>
</body>
实现效果:
生成过程:
- 数据
- 模版
- 数据+模版结合,生成真实的DOM,来显示
- 数据发生改变
- 数据+模版结合,生成真实的DOM,替换原始的DOM
当p标签中内容DOM1改变为DOM2时,实际上是重新构建了DOM树进行替换,极大的增加了消耗
缺陷:
- 第一次生成了一个完整的DOM片段
- 第二次生成了一个完整的DOM片段
- 第二次的DOM替换第一次的DOM,非常耗性能
虚拟DOM的构建
<body>
<div id="example"></div>
<script type="text/babel">
const divReact = document.getElementById('example');
const reactH3 = React.createElement('h3', {}, '虚拟的DOM');
const reactP = React.createElement('p', {}, 'DOM3');
const reactSpan = React.createElement('span', {}, reactH3, reactP);
ReactDOM.render(reactSpan, divReact);
</script>
</body>
JSX语法
<body>
<div id="react-div"></div>
<script type="text/babel">
const reactDiv = document.getElementById('react-div');
const reactSpan = (
<span>
<h3>JSX语法</h3>
</span>
);
ReactDOM.render(reactSpan, reactDiv);
</script>
</body>
- state数据
- JSX模版
- 数据+模版生成虚拟DOM(虚拟DOM就是一个JS对象,用它来描述真实DOM)(损耗了性能)
[ 'div' , {id: 'abc'}, [ 'span', {}, 'hello world']]
- 用虚拟DOM的结构生成真实的DOM,来显示
<div id='abc'><span>hello world</span></div>
- state 发生变化
- 数据+模版生成新的虚拟DOM(极大的提升了性能)
[ 'div', {id: 'abc'}, [ 'span' , {},'bye bye' ]]
- 比较原始虚拟DOM和新的虚拟DOM的区别,找到区别是span中内容(极大的提升性能)
- 直接操作DOM,改变span中的内容
DOM生成过程
JSX -->createElement -->虚拟DOM (JS 对象)–>真实的DOM
虚拟DOM中的diff
在实际的代码中,会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个唯一的标记。在深度优先遍历的时候,每遍历到一个节点就把该节点和新的的树进行对比。如果有差异的话就记录到一个对象里面。
diff算法中只会比较同层级的元素,一旦发现某一级别之间有所不同,则会弃置其子级,直接用新的差异的一级以及其下的所有子级替换老的。 好处:算法简单,也就提高了比对速度,因此最后也就提升了性能。
关于DOM的误区
有些人认为DOM操作慢,虚拟 DOM快。
这句话类似于:刘翔矮(对比于姚明)。
DOM操作慢是对比于JS原生API,如数组操作。
任何基于DOM的库(Vue/React)都不可能在操作DOM时比 DOM快。
只有在以下情况虚拟DOM是比DOM快的:
虚拟DOM可以将多次操作合并为一次操作。
虚拟DOM借助DOM diff可以把多余的操作省掉。
虚拟DOM优点
1. 性能提升了。
减少DOM操作次数:
虚拟DOM可以将多次操作合并为一次操作。当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。比如想添加1000个节点,本来应该是一个接一个操作,虚拟DOM只用操作一次
减少DOM操作范围:
虚拟DOM借助DOM diff可以把多余的操作省掉。更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免大量的无谓计算。比如想添加100个节点,发现本来页面上有需要的90个节点,其实只有10个是更新后新增的,不用再去操作剩余的90个节点。
2. 使得跨端应用得以实现。React Native
虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI,因为虚拟DOM本质上只是一个JS对象。
虚拟DOM缺点
需要额外的创建函数,如createElement,但可以通过JSX来简化成XML写法
React.createElement(‘li’,{onClick:this.addList.bind(this)},`${你好}`)
JSX语法:通过babel转为createElement形式
<li onClick={this.addList}>你好</li>