高性能JavaScript(3)

DOM是一个独立于语言的,用于操作XML和HTML文档的程序接口,尽管DOM是个独立语言的API,但它在浏览器中的接口却是用JavaScript实现的。浏览器通常会把DOM和JavaScript独立实现,例如Chrome的DOM渲染使用的WebKit来实现的,但JavaScript引擎使用的是V8。由于是两个相互独立的功能,两者之间通过接口连接,因此在JavaScript中访问DOM就会产生消耗,天生就慢。

重绘与重排

浏览器中存在两个数据接口:

  • DOM树

页面结构

  • 渲染树

表示DOM树的显示

一旦DOM和渲染树构建完成,浏览器就开始绘制。当DOM的变化影响了元素的几何属性,浏览器需要重新计算元素的几何属性,同时其他元素的几何属性和位置也会受到影响。这是浏览器会发生“重排”,重新构建渲染树,重排之后,浏览器将重新绘制受到影响的部分到屏幕上,称为“重绘”。

不是所有的DOM变化都会导致重排,例如改变元素的背景色,这种情况下只会发生一次重绘。

导致重排的因素:

  • 元素的显隐
  • 元素位置的改变
  • 元素尺寸的改变(内外边距,边框宽度,高度宽度)
  • 元素内容改变
  • 页面初始化
  • 浏览器窗口尺寸的改变

优化重排和重绘

合并样式修改

较慢

1
2
3
var el = document.getElementById('div')
el.style.borderLeft = '1px'
el.style.borderRight = '2px'

优化

1
2
var el = document.getElementById('div')
el.style.cssText += ';border-left: 1px; border-right: 2px'

或者通过改变class

1
2
var el = document.getElementById('div')
el.className = 'change'

批量修改DOM

批量修改DOM的通用步骤:

  1. 元素脱离文档
  2. 进行修改
  3. 插入文档

例如存在如下DOM结构

1
2
3
4
<ul>
<li>aaa</li>
<li>bbb</li>
</ul>

现要根据数据渲染li结构,以下是修改li节点的函数

1
2
3
4
5
6
7
8
9
function renderList(parentNode, data) {
var li
for (var i = 0; i < data.length; i++) {
li = document.createElement('li')
// 根据数据生成li内容
...
parentNode.appendChild(li)
}
}

较慢

1
2
3
var data = [1, 2, 3]
var ul = document.getElementById('ul')
renderList(ul, data)

使用上述方法,每次插入一个li都会导致重排,我们可以按之前的通用方式进行优化

1
2
3
4
5
var data = [1, 2, 3]
var ul = document.getElementById('ul')
ul.style.display = 'none'
renderList(ul, data)
ul.style.display = 'block'

或者通过创建代码片段进行优化

1
2
3
4
5
var data = [1, 2, 3]
var ul = document.getElementById('ul')
var fragment = document.createDocumentFragment()
renderList(fragment, data)
ul.appendChild(fragment)

元素脱离动画流

事件委托

基于事件的冒泡机制,在父级节点绑定处理器,处理子元素的事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var parent = document.getElementById('parent')
parent.onclick = function(evt) {
evt = evt || window.event
var target = evt.target || evt.srcElement // 实际触发的元素
// 判断元素,执行事件处理过程
...
if (typeof evt.preventDefault === 'function') {
evt.preventDefault()
evt.stopPropagetion()
} else {
evt.returnValue = false
evt.cancelBubble = false
}
}