浅谈虚拟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的设计思想:

  1. 提供一种方便的工具,使开发效率得到保证;
  2. 保证最小化的DOM操作,使得执行效率得到保证。

Virtual DOM 算法。包括几个步骤:

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  3. 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

原生DOM vs virtual DOM

Dom对象一个空的div元素的第一层属性列表:

 

onvolumechange:

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的一些理解,不足之处,可以指出,一起探讨,共同进步。

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值