我们知道,对DOM的操作都是非常的耗性能的,那么为什么会耗性能呢?
文档对象模型(DOM)是一个独立于语言的,使用 XML和 HTML 文档操作的应用程序接口(API)。在浏览器中,主要与 HTML 文档打交道,在网页应用中检索 XML 文档也很常见。DOM APIs 主要用于访问这些文档中的数据。尽管 DOM 是与语言无关的 API,在浏览器中的接口却是以 JavaScript 实现的。客户端大多数脚本程序与文档打交道,DOM 就成为 JavaScript 代码日常行为中重要的组成部分。
一、重绘和重排版
当浏览器加载完所有页面 HTML标签,JavaScript,CSS之后,它开始解析文件并创建两个内部数据结构: 一棵 DOM 树表示页面结构 ;一棵渲染树表示 DOM 节点如何显示。渲染引擎首先解析HTML文档,转换为一棵DOM树,此为第一步。接下来不管是内联式,外联式还是嵌入式引入的CSS样式也会被解析,渲染出另 外一棵用于渲染DOM树的树-渲染树(render tree) ,渲染树包含带有颜色,尺寸等显示属性的矩形,这些矩形的顺序与显示顺序一致。渲染树中为每个需要显示的DOM树节点存放至少一个节点(隐藏 DOM 元素在渲染树中没有对应节)。渲染树上的节点称为“框”或者“盒”,符合 CSS 模型的定义,将页面元素看作一个具有填充、边距、框和位置的盒。一旦 DOM 树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。
当 DOM 改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。
不是所有的 DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。 重绘和重排版是负担很重的操作,可能导致网页应用的用户界面失去响应。那么,什么时候会发生重排版呢?
1、添加或删除可见的 DOM 元素 ;
2、元素位置改变 ;
3、元素尺寸改变(因为边距,填充,边框宽度,宽度,高度等属性改变);
4、内容改变,例如,文本改变或图片被另一个不同尺寸的所替代 ;
5、最初的页面渲染;
6、浏览器窗口改变尺寸。
根据改变的性质,渲染树上或大或小的一部分需要重新计算。某些改变可导致重排版整个页面:例如,当一个滚动条出现时。
因为计算量与每次重排版有关,大多数浏览器通过队列化修改和批量显示优化重排版过程。然而,你可能(经常不由自主地)强迫队列刷新并要求所有计划改变的部分立刻应用。获取布局信息的操作将导致刷新队列动作,这意味着使用了下面这些方法:
• offsetTop, offsetLeft, offsetWidth, offsetHeight
• scrollTop, scrollLeft, scrollWidth, scrollHeight
• clientTop, clientLeft, clientWidth, clientHeight
• getComputedStyle() (currentStyle in IE)(在 IE 中此函数称为 currentStyle)
布局信息由这些属性和方法返回最新的数据, 所以浏览器不得不运行渲染队列中待改变的项目并重新排版以返回正确的值。
二、最小化重绘和重排版
var el = document.getElementById('mydiv'); el.style.borderLeft = '1px'; el.style.borderRight = '2px'; el.style.padding = '5px';
这里改变了三个风格属性,每次改变都影响到元素的几何属性。在这个糟糕的例子中,它导致浏览器重排版了三次。大多数现代浏览器优化了这种情况只进行一次重排版,但是在老式浏览器中,或者同时有一个分离的同步进程(例如使用了一个定时器),效率将十分低下。如果其他代码在这段代码运行时查询布局信息,将导致三次重布局发生。而且,此代码访问 DOM 四次,可以被优化。 一个达到同样效果而效率更高的方法是:将所有改变合并在一起执行,只修改 DOM 一次。可通过使用cssText 属性实现:
var el = document.getElementById('mydiv'); el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
另一个一次性改变风格的办法是修改 CSS 的类名称,而不是修改内联风格代码。这种方法适用于那些风格不依赖于运行逻辑,不需要计算的情况。改变 CSS 类名称更清晰,更易于维护;它有助于保持脚本免除显示代码,虽然它可能带来轻微的性能冲击,因为改变类时需要检查级联表。
var el = document.getElementById('mydiv'); el.className = 'active';
三、批量修改 DOM
当你需要对 DOM 元素进行多次修改时,你可以通过以下步骤减少重绘和重排版的次数:
1、从文档流中摘除该元素 ;
2、对其应用多重改变;
3、将元素带回文档中 。
此过程引发两次重排版——第一步引发一次,第三步引发一次。如果你忽略了这两个步骤,那么第二步中每次改变都将引发一次重排版。
有三种基本方法可以将 DOM 从文档中摘除:
1、隐藏元素,进行修改,然后再显示它;
var ul = document.getElementById('mylist'); ul.style.display = 'none'; appendDataToElement(ul, data); ul.style.display = 'block';
2、使用一个文档片断在已存 DOM 之外创建一个子树,然后将它拷贝到文档中;
var fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); document.getElementById('mylist').appendChild(fragment);
3、将原始元素拷贝到一个脱离文档的节点中,修改副本,然后覆盖原始元素。
var old = document.getElementById('mylist'); var clone = old.cloneNode(true); appendDataToElement(clone, data); old.parentNode.replaceChild(clone, old);
ps:以上内容总结于《高性能javascript编程》