虚拟Dom
Posted lenhartGG
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虚拟Dom相关的知识,希望对你有一定的参考价值。
序言:
七月底,继梅雨天气过后,南方的气温可谓直线飙升。南方的夏天不像北方那么干热,空气中弥漫大量的水汽,仿佛处于桑拿房一样。何以降温,唯有空调。很久没写博客了,加上最近天气炎热,最近整个人都变懒了,很颓,所以想想还是把之前的笔记整理一下。
今天简单说下我个人对于虚拟DOM的一些理解,不足之处,希望各位大神提出不足,抒发己见,一起学习探讨,共同进步。
什么是DOM?现在前端为何要构建虚拟DOM而放弃直接操作DOM?
首先理解什么是DOM,DOM全称是 Document Object Model,也就是文档对象模型。说白了,DOM对象本身也是一个js对象,所以严格说,并不是操作dom这个对象慢,而是操作这个对象后,所触发浏览器的一些行为,导致页面不能即使展现在用户面前。例如:布局(layout)和绘制(paint)。
浏览器是如何呈现一个界面的?
一个浏览器有许多模块,其中负责呈现页面的是渲染引擎模块,比较熟悉的有WebKit和Gecko等。
浏览器呈现一个页面的大致过程:
解析html,并生成一棵DOM tree
解析各种样式并结合DOM tree生成一棵Render tree
对Render tree的各个节点计算布局信息,比如box的位置与尺寸
根据Render tree并利用浏览器的UI层进行绘制
其中DOM tree和Render tree上的节点并非一一对应,比如一个"display:none"的节点就只会存在于DOM tree上,而不会出现在Render tree上,因为这个节点不需要被绘制。
绘制(paint)是一个耗时的过程,然而布局(layout)是一个更耗时的过程,我们无法确定layout一定是自上而下或是自下而上进行的,甚至一次layout会牵涉到整个文档布局的重新计算。
但是layout是肯定无法避免的,所以我们主要是要最小化layout的次数。
什么情况下浏览器会进行layout?
一般情况下,浏览器的layout是lazy的,也就是说:在js脚本执行时,是不会去更新DOM的,任何对DOM的修改都会被暂存在一个队列中,在当前js的执行上下文完成执行后,会根据这个队列中的修改,进行一次layout。然而有时希望在js代码中立刻获取最新的DOM节点信息,浏览器就不得不提前执行layout,这是导致DOM性能问题的主因。
如下的操作会打破常规,并触发浏览器执行layout:
通过js获取需要计算的DOM属性
添加或删除DOM元素
resize浏览器窗口大小
改变字体
css伪类的激活,比如:hover
通过js修改DOM元素样式且该样式涉及到尺寸的改变
原生中对于减少layout的一些方法
针对一系列DOM操作(DOM元素的增删改),可以有如下方案:
documentFragment
display: none
cloneNode
比如(仅以documentFragment为例):
var fragment = document.createDocumentFragment();
for (var i=0; i < items.length; i++){
var item = document.createElement("li");
item.appendChild(document.createTextNode("Option " + i);
fragment.appendChild(item);
}
list.appendChild(fragment);
这类优化方案的核心思想都是相同的,就是先对一个不在Render tree上的节点进行一系列操作,再把这个节点添加回Render tree,这样无论多么复杂的DOM操作,最终都只会触发一次layout。
使用requestAnimationFrame
任何可能导致重绘的操作都应该放入requestAnimationFrame
在现实项目中,代码按模块划分,很难像上例那样组织批量读写。那么这时可以把写操作放在requestAnimationFrame的callback中,统一让写操作在下一次paint之前执行。
诸如此类的一些原生的优化dom操作的方式还有很多,那么为什么我们要用react或者vue呢?virtual DOM又有什么好处?
首先用了requestAnimationFrame后就变成了异步编程的问题了。如果我们要让读写状态同步,那必然需要在DOM的基础上写个Wrapper来内部控制异步读写,不过这是很麻烦的。
而react作用就是可以帮我们解决这种问题。因为react中这些优化是默认实施的,这不用再去进行那些复杂的操作,而且性能也很好。前端框架的存在就是为了减轻程序员的工作,让编程更加容易,另一方面使用框架可以让我们的项目更易去维护。
而且,DOM 完全不属于javascript (也不在Javascript 引擎中存在).。Javascript其实是一个非常独立的引擎,DOM其实是浏览器引出的一组让Javascript操作HTML文档的API而已。在即时编译的时代,调用DOM的开销是很大的。而Virtual DOM的执行完全都在Javascript 引擎中,完全不会有这个开销。
React.js 相对于直接操作原生DOM有很大的性能优势,很大程度上都要归功于virtual DOM的batching 和diff。batching把所有的DOM操作搜集起来,一次性提交给真实的DOM。Diff算法通过对比新旧虚拟DOM树记录之间的差异
diff算法时间复杂度也从标准的的Diff算法的O(n^3)降到了O(n)。
虚拟DOM的设计思想:
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
原生DOM vs virtual DOM
Dom对象一个空的div元素的第一层属性列表:
像onvolumechange
事件在视频/音频(audio/video)的音量发生改变时触发。
该事件可以通过以下方式调用:
· 增大或降低音量
· 媒体播放器静音或解除静音
有必要让每一个 div 节点都包括 onvolumechange
属性吗?这个是错误的吗?答案是否定的,这只是老旧的 DOM Level 0 版本中传统事件模型里的一个事件回调,它“必须为所有的HTML 元素所支持,既作为内容属性也作为 IDL(IDL是Interface description language的缩写,指接口描述语言,是CORBA规范的一部分,是跨平台开发的基础。) 属性”,定义于 W3C的 HTML 规范中的 Section 6.1.6.2,无可回避。
与之对应的,以下是 React 中伪 DOM div 元素的第一层属性列表:
可以看出虚拟DOM需要计算的属性相对原生而言会少很多,这样效率就很快,每次操作之后先修改虚拟dom结构,通过对比之前的状态,计算出最小的dom修改步骤,再通知真实DOM做最小的修改。虚拟DOM操作最后归根结底还是作用到真实 DOM操作。
打个可能不太恰当的比方,一个菜鸟级沙画师,一次只会画一张,要改变就只能抹平重新画,而Virtual DOM就相当于模板画,diff算法,好比一个大师在旁指导,告诉这个菜鸟你不需要抹平重画,只需改掉一些地方就能实现下一张图的效果。
以上是个人对虚拟DOM的一些理解,不足之处,可以指出,一起探讨,共同进步。
以上是关于虚拟Dom的主要内容,如果未能解决你的问题,请参考以下文章
jquery 对象的 heightinnerHeightouterHeight 的区别以及DOM 元素的 clientHeightoffsetHeightscrollHeightoffset(代码片段