怎么更好的理解虚拟DOM?

虽然Virtual DOM确实是性能杠杠的,但是其实可以说它是无心插柳的一个结果。

React的核心思想:
一个Component拯救世界,忘掉烦恼,从此不再操心界面。

1. Virtual Dom快,有两个前提
1.1 Javascript很快
Chrome刚出来的时候,在Chrome里跑Javascript非常快,给了其它浏览器很大压力。而现在经过几轮你追我赶,各主流浏览器的Javascript执行速度都很快了。
Julia有一个Benchmark,Julia Benchmarks, 可以看到Javascript跟C语言很接近了,也就几倍的差距,跟Java基本也是一个量级。
所以说,单纯的Javascript其实速度是很快的。
多说一句,这种benchmark并不是绝对的依据,因为用这个语言写这个跑得快,并不代表一定是用这个语言写那个也跑得快。

1.2 DOM很慢
关于什么CSS,什么layout那些我不懂,就不瞎说了,咱就说说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是这样,
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
你想把它变成这样
<ul>
<li>6</li>
<li>7</li>
<li>8</li>
<li>9</li>
<li>10</li>
</ul>
通常的操作是什么?
先把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操作快多了吧?

当然还有其它一些例子能够优化我们对DOM的操作,就不举例子了。(实际上是因为我举不出例子。。。)

2. 关于React
2.1 接口和设计
在React的设计里,是完全不需要你操作DOM的。在React里其实根本就没有DOM这个概念的存在,只有Component。当你写好一个Component以后,Component会完全负责UI,你不需要也不应该去也不能够指挥Component怎么显示,你只能告诉它你想要显示一个香蕉还是两个梨。
隔离DOM并不是因为DOM慢(当然DOM确实慢),而是把界面和业务完全隔离,操作数据的只关心数据,操作界面的只关心界面。可以想象成把MVC里面的Controller分成两个部分,一部分合并到M里面去,一部分合并到V里面去,就剩下MV,没有C了。。。其实M也并不是Model了。推荐看一下Pete Hunt的这个Talk 

重复一遍,React的意思是,我提供一个Component,然后你只管给我数据,界面的事情完全不用你操心,我保证会把界面变成你想要的样子。
你可以把一个React的Component想象成一个Pure Function,只要你给的数据是[1, 2, 3],我保证显示的是[1, 2, 3]。没有什么删除一个Element,添加一个Element这样的事情。NO。你要我显示什么就给我一个完整的列表。

说到这里,插一句别的,我一开始看到这里还以为这样的处理方式比较适合一般的WEB应用,写游戏啊什么的可能这个模式不太好用,然后我就看到Pete Hunt那个Talk,说DOOM 3就是这么干的。

。。
。。。
眼泪都下来了,大神们的思路果然我是摸不着边的,洗洗睡吧。

睡醒了接着说。React其实需要从Imperative Programming转换到Declarative Programming去理解。你不要一步一步告诉我这件事情怎么做,什么先和面再剁馅,NO,告诉我你想要煎饼还是月饼,我会想办法去做的,不要来干扰我。你只需要告诉我有这么一个列表[1, 3, 6]需要显示就行了,不要告诉我怎么显示,我会想办法的,我保证美得冒泡,各种神奇的效果,亮瞎你的钛合金狗眼。

行了行了,你真啰嗦。

。。。

再说几句瞎扯的话,Flux虽然说的是单向的Data Flow,但是实际上就是单向的Observer。
Store->View->Action->Store(箭头是数据流向,实现上可以理解为View监听Store,View直接trigger action,然后Store监听Action)
等等,不是说Component是pure function不跟谁绑定吗,为啥View要监听Store?你这个骗子。怪不得都没有人给你点赞。
。。。
。。

我们还是继续说React把,Flux是什么鬼,我反正没听过。


2.2 实现
OK,那么,如何实现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操作省下来的时间依然很可观。所以总体上来说,还是比较快。

简单发散一下思路,如果哪一天,DOM本身的已经操作非常非常非常快了,并且我们手动对于DOM的操作都是精心设计优化过后的,那么加上了VirtualDom还会快吗?
当然不行了,毕竟你多做了这么多额外的工作。

但是那一天会来到吗?
诶,大不了到时候不用Virtual DOM。

原文地址:https://www.cnblogs.com/daqianduan/p/4759562.html