为什么说 DOM 操作很慢
2016-03-14 10:42
281 查看
作者:leozdgao
网址:http://leozdgao.me/why-dom-slow/
浏览器是如何呈现一张页面的
一个浏览器有许多模块,其中负责呈现页面的是渲染引擎模块,比较熟悉的有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上,因为这个节点不需要被绘制。
![](http://s4.51cto.com/wyfs02/M00/7D/5E/wKioL1bnVvOAybBeAABDF7SnTz4676.jpg)
上图是Webkit的基本流程,在术语上和Gecko可能会有不同,这里贴上Gecko的流程图,不过文章下面的内容都会统一使用Webkit的术语。
![](http://s3.51cto.com/wyfs02/M01/7D/60/wKiom1bnVnThYSlxAABL_5nhgG4600.jpg)
影响页面呈现的因素有许多,比如link的位置会影响首屏呈现等。但这里主要集中讨论与layout相关的内容。paint是一个耗时的过程,然而layout是一个更耗时的过程,我们无法确定layout一定是自上而下或是自下而上进行的,甚至一次layout会牵涉到整个文档布局的重新计算。但是layout是肯定无法避免的,所以我们主要是要最小化layout的次数。什么情况下浏览器会进行layout在考虑如何最小化layout次数之前,要先了解什么时候浏览器会进行layout。layout(reflow)一般被称为布局,这个操作是用来计算文档中元素的位置和大小,是渲染前重要的一步。在HTML第一次被加载的时候,会有一次layout之外,js脚本的执行和样式的改变同样会导致浏览器执行layout,这也是本文的主要要讨论的内容。一般情况下,浏览器的layout是lazy的,也就是说:在js脚本执行时,是不会去更新DOM的,任何对DOM的修改都会被暂存在一个队列中,在当前js的执行上下文完成执行后,会根据这个队列中的修改,进行一次layout。然而有时希望在js代码中立刻获取最新的DOM节点信息,浏览器就不得不提前执行layout,这是导致DOM性能问题的主因。如下的操作会打破常规,并触发浏览器执行layout:通过js获取需要计算的DOM属性
添加或删除DOM元素
resize浏览器窗口大小
改变字体
css伪类的激活,比如:hover
通过js修改DOM元素样式且该样式涉及到尺寸的改变
我们来通过一个例子直观的感受下:
// Readvar h1 = element1.clientHeight; // Write (invalidates layout)element1.style.height = (h1 * 2) + 'px'; // Read (triggers layout)var h2 = element2.clientHeight; // Write (invalidates layout)element2.style.height = (h2 * 2) + 'px'; // Read (triggers layout)var h3 = element3.clientHeight; // Write (invalidates layout)element3.style.height = (h3 * 2) + 'px';
这里涉及一个属性clientHeight,这个属性是需要计算得到的,于是就会触发浏览器的一次layout。我们来利用chrome(v47.0)的开发者工具看下(截图中的timeline record已经经过筛选,仅显示layout):
![](http://s3.51cto.com/wyfs02/M01/7D/5E/wKioL1bnV5SitPaZAAC62OUx77o729.jpg)
// Readvar h1 = element1.clientHeight; var h2 = element2.clientHeight; var h3 = element3.clientHeight; // Write (invalidates layout)element1.style.height = (h1 * 2) + 'px'; element2.style.height = (h2 * 2) + 'px'; element3.style.height = (h3 * 2) + 'px';看下这次的情况:
![](http://s4.51cto.com/wyfs02/M02/7D/60/wKiom1bnVz_i1KISAACnOarcJqA623.jpg)
下篇再介绍一些其他的优化方案
网址:http://leozdgao.me/why-dom-slow/
浏览器是如何呈现一张页面的
一个浏览器有许多模块,其中负责呈现页面的是渲染引擎模块,比较熟悉的有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上,因为这个节点不需要被绘制。
![](http://s4.51cto.com/wyfs02/M00/7D/5E/wKioL1bnVvOAybBeAABDF7SnTz4676.jpg)
上图是Webkit的基本流程,在术语上和Gecko可能会有不同,这里贴上Gecko的流程图,不过文章下面的内容都会统一使用Webkit的术语。
![](http://s3.51cto.com/wyfs02/M01/7D/60/wKiom1bnVnThYSlxAABL_5nhgG4600.jpg)
影响页面呈现的因素有许多,比如link的位置会影响首屏呈现等。但这里主要集中讨论与layout相关的内容。paint是一个耗时的过程,然而layout是一个更耗时的过程,我们无法确定layout一定是自上而下或是自下而上进行的,甚至一次layout会牵涉到整个文档布局的重新计算。但是layout是肯定无法避免的,所以我们主要是要最小化layout的次数。什么情况下浏览器会进行layout在考虑如何最小化layout次数之前,要先了解什么时候浏览器会进行layout。layout(reflow)一般被称为布局,这个操作是用来计算文档中元素的位置和大小,是渲染前重要的一步。在HTML第一次被加载的时候,会有一次layout之外,js脚本的执行和样式的改变同样会导致浏览器执行layout,这也是本文的主要要讨论的内容。一般情况下,浏览器的layout是lazy的,也就是说:在js脚本执行时,是不会去更新DOM的,任何对DOM的修改都会被暂存在一个队列中,在当前js的执行上下文完成执行后,会根据这个队列中的修改,进行一次layout。然而有时希望在js代码中立刻获取最新的DOM节点信息,浏览器就不得不提前执行layout,这是导致DOM性能问题的主因。如下的操作会打破常规,并触发浏览器执行layout:通过js获取需要计算的DOM属性
添加或删除DOM元素
resize浏览器窗口大小
改变字体
css伪类的激活,比如:hover
通过js修改DOM元素样式且该样式涉及到尺寸的改变
我们来通过一个例子直观的感受下:
// Readvar h1 = element1.clientHeight; // Write (invalidates layout)element1.style.height = (h1 * 2) + 'px'; // Read (triggers layout)var h2 = element2.clientHeight; // Write (invalidates layout)element2.style.height = (h2 * 2) + 'px'; // Read (triggers layout)var h3 = element3.clientHeight; // Write (invalidates layout)element3.style.height = (h3 * 2) + 'px';
这里涉及一个属性clientHeight,这个属性是需要计算得到的,于是就会触发浏览器的一次layout。我们来利用chrome(v47.0)的开发者工具看下(截图中的timeline record已经经过筛选,仅显示layout):
![](http://s3.51cto.com/wyfs02/M01/7D/5E/wKioL1bnV5SitPaZAAC62OUx77o729.jpg)
// Readvar h1 = element1.clientHeight; var h2 = element2.clientHeight; var h3 = element3.clientHeight; // Write (invalidates layout)element1.style.height = (h1 * 2) + 'px'; element2.style.height = (h2 * 2) + 'px'; element3.style.height = (h3 * 2) + 'px';看下这次的情况:
![](http://s4.51cto.com/wyfs02/M02/7D/60/wKiom1bnVz_i1KISAACnOarcJqA623.jpg)
下篇再介绍一些其他的优化方案
相关文章推荐
- jQuery中ready与load事件的区别
- 数据结构第一次上机实践
- 计蒜课难题题库之奇怪的国家
- Slickflow.NET 开源工作流引擎基础介绍(四) -- 多数据库支持实现
- tomcat deploy部署项目三种方法
- AJAX相关代码及使用JOSN格式时,必须设置字符集,否则会乱码
- 3动画方法
- android studio mipmap 目录和 drawable 目录有什么区别
- HDU 1166 敌兵布阵【线段树 单点更新】
- redis数据导出,报错Could not connect to Redis at ip:port: Cannot assign requested address解决方法
- MVC从路由中匹配出Controler的名称
- TCP三次握手
- 采购接收直接入库interface导入
- android中activity切换时白屏问题
- 回文串问题
- Hibernate关联关系配置(一对多、一对一和多对多)
- JAVA设计模式之单例模式
- JS获取IP、MAC和主机名的几种方法
- OpenCV——GMM混合高斯模型
- 堆和栈的区别(转过无数次的文章)