盒子
文章目录
  1. Virtual Dom快的前提
  2. 关于React
  3. Virtual Dom到底做了什么

Virtual Dom简介

总结:为了防止太长不看,简单总结一下Virtual Dom到底做了什么。DOM操作很慢是两个原因,一个是本身操作就不快,第二是我们(还有很多框架)处理dom的方式很慢,Virtual Dom解决了我们这些愚蠢的程序员对Dom的低劣操作,它让我们不需要进行Dom操作,而是将希望展现的最终结果告诉React,React通过一个简化的Dom即Virtual dom进行render,当你试图改变显示内容时,新生成的Virtual Dom会与现在的Virtual dom对比,通过diff算法找到区别,这些操作都是在快速的js中完成的,最后对实际Dom进行最小的Dom操作来完成效果,这就是Virtual Dom的概念。

Virtual Dom快的前提

1.1 Javascript很快

Julia有一个Benchmark,Julia Benchmarks, 可以看到Javascript跟C语言很接近了,也就几倍的差距,跟Java基本也是一个量级。

1.2 DOM很慢

当你用document.createElement()创建一个空的Element的时候(比如创建一个空的div),有以下这几页的东西需要实现(当然,这不是标准,只是个大概的意思):

HTMLElement - Web API Interfaces**

Element - Web API Interfaces**

GlobalEventHandlers**

非常非常多,并且还有不少嵌套引用。

你可以在Chrome console里手动调用document.createElement 然后插入DOM里看看效果。

这还是一个空的Elemnt,啥内容也没有,就这么复杂。所以说DOM的操作非常慢是可以理解的。不是浏览器不想好好实现DOM,而是DOM设计得太复杂,没办法。

而更糟糕的是,我们(以及很多框架)在调用DOM的API的时候做得不好,导致整个过程更加的慢。React的Virtual Dom解决的是这一部分问题,它并不能解决DOM本身慢的问题。

比如说,现在你的list是这样,



  • 0


  • 1


  • 2


  • 3


你想把它变成这样



  • 6


  • 7


  • 8


  • 9


  • 10


通常的操作是什么?

先把0, 1,2,3这些Element删掉,然后加几个新的Element 6,7,8,9,10进去,这里面就有4次Element删除,5次Element添加。

而React会把这两个做一下Diff,然后发现其实不用删除0,1,2,3,而是可以直接改innerHTML,然后只需要添加一个Element(10)就行了,这样就是4次innerHTML操作加1个Element添加,比9次Element操作快多了吧?

关于React

2.1 接口和设计

在React的设计里,是完全不需要你操作DOM的。在React里其实根本就没有DOM这个概念的存在,只有Component。当你写好一个Component以后,Component会完全负责UI,你不需要也不应该去也不能够指挥Component怎么显示,你只能告诉它你想要显示一个香蕉还是两个梨。

2.2 实现

那么,如何实现React呢?其实对于React来说,最容易实现的办法是每次完全摧毁整个DOM,然后重新建立一个全新的DOM。因为一个Component是一个Pure function,根本就没有State这个概念,我又不知道DOM现在是什么样子,那最简单的办法当然是只要你给新数据,我就把整个DOM删了,然后根据你给的数据重新生成一个DOM咯。

等等,Virtual DOM哪儿去了?

事实是这样的,最简单实现React的方式虽然说非常简单,但是效率实在是太低了,你居然要全部都删了重建DOM,DOM本身已经很慢了,你还这么去用,谁能忍啊?

然后Virtual DOM就来救场了。

Virtual DOM和DOM是啥关系呢?

首先,Virtual DOM并没有完全实现DOM,Virtual DOM最主要的还是保留了Element之间的层次关系和一些基本属性。因为DOM实在是太复杂,一个空的Element都复杂得能让你崩溃,并且几乎所有内容我根本不关心好吗。所以Virtual DOM里每一个Element实际上只有几个属性,并且没有那么多乱七八糟的引用。所以哪怕是直接把Virtual DOM删了,根据新传进来的数据重新创建一个新的Virtual DOM出来都非常非常非常快。(每一个component的render函数就是在做这个事情,给新的virtual dom提供input)

所以,引入了Virtual DOM之后,React是这么干的:

你给我一个数据,我根据这个数据生成一个全新的Virtual DOM,然后跟我上一次生成的Virtual DOM去 diff,得到一个Patch,然后把这个Patch打到浏览器的DOM上去。完事。

有点像版本控制打patch的思路。

假设在任意时候有,VirtualDom1 == DOM1 (组织结构相同)

当有新数据来的时候,我生成VirtualDom2,然后去和VirtualDom1做diff,得到一个Patch。

然后将这个Patch去应用到DOM1上,得到DOM2。

如果一切正常,那么有VirtualDom2 == DOM2。

这里你可以做一些小实验,去破坏VirtualDom1 == DOM1这个假设(手动在DOM里删除一些Element,这时候VirtualDom里的Element没有被删除,所以两边不一样了)。

然后给新的数据,你会发现生成的界面就不是你想要的那个界面了。

最后,回到为什么Virtual Dom快这个问题上。

其实是由于每次生成virtual dom很快,diff生成patch也比较快,而在对DOM进行patch的时候,我能够根据Patch的内容,优化一部分DOM操作,比如之前1.2里的那个例子。

重点就在最后,哪怕是我生成了virtual dom,哪怕是我跑了diff,但是我根据patch简化了那些DOM操作省下来的时间依然很可观。所以总体上来说,还是比较快。

Virtual Dom到底做了什么

浏览器上呈现的html文档,本质上来说是一种xml,那么我们可以用一种树状结构把这个html文档描述出来:

1
2
3
4
5
6
7
8
9
10
html
+----head
+ +----...
+
+----body
+----div
+ +----...
+
+----div
+----...

React在呈现的过程中,会首先根据render的结果将这个树状结构在js里创建出来(注意,这个时候并没有操作DOM),这个树状结构就是虚拟DOM层。根据一个array[1, 2, 3, 4, 5]去渲染一个列表,那么虚拟DOM应该类似以下结构:

1
2
3
4
5
6
ul
+----li(1)
+----li(2)
+----li(3)
+----li(4)
+----li(5)

这时再根据这个虚拟DOM渲染成实际DOM。然后重点来了,如果array内容发生变化了怎么办,比如我们删除了3这个元素,重新render,虚拟DOM会发生相应变化:

1
2
3
4
5
ul
+----li(1)
+----li(2)
+----li(4)
+----li(5)

React会将这个新的虚拟DOM和正在呈现的虚拟DOM进行对比,并找出其中的差异,然后用最少的DOM操作完成这个更新。

这里需要注意到,以上这些操作都是在js里完成的(生成虚拟DOM,比对),并没有实际操作DOM元素,比对完毕后找出的差异才会实际操作DOM元素,比如移除掉一个节点,更新其他节点的属性。

对比angularjs,没有比对的这个过程,直接移除掉所有模板元素的DOM,再重新添加。我们曾经结合angularjs和d3js,模板中有一个占位符用d3js来绘制图表,结果数据变化时,d3js绘制的图表被移除了,替换回了占位符,不得不重新绘制图表,而不是更新图表数据,结果就是图表会有一瞬间的空白状态。

具体的diff算法React的diff算法,防白板:http://blog.jobbole.com/73701/

以上内容整理自知乎用户EMayej Bee周聪怎么更好的理解虚拟DOM中的回答