# 3. DOM编程

  1. 把DOM和ECMAscript各自想象成一个岛屿,它们之间用收费桥梁连接。ECMAscript每次访问DOM,都要途径这座桥,并交纳过桥费。访问DOM次数越多,费用也就越高。访问DOM的次数越多,代码的运行速度越慢。因此,最好的做法是:减少访问DOM的次数,把运算尽量留在ECMAscript这一端处理。
  2. 在旧版本浏览器中,innerHTML比原生DOM方法(document.createElement())快的多,但在基于webkit内核的浏览器中相反:用DOM方法略胜一筹。
  3. 节点克隆
  var dupNode = node.cloneNode(deep);
  // node
  // 将要被克隆的节点
  // dupNode
  // 克隆生成的副本节点
  // deep 可选
  // 是否采用深度克隆,如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身.
  element.cloneNode(false)
  element.cloneNode(true)
1
2
3
4
5
6
7
8
9
  1. 读取一个集合的length比读取普通数组的length要慢的多,因为每次都要重新计算。
  2. 使用children替代childNodes会更快,因为集合项更少。HTML源码中的空白实际上是文本节点,而且它并不包含在children集合中。

# 重排(reflow)与重绘(repaint)

当DOM的变化影响了元素的几何属性(宽和高),浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。这个过程是重排。
重排后,浏览器会重新绘制受影响的部分到屏幕中,这个过程称为重绘。

# 重排何时发生

  • 添加或删除可见的DOM元素。
  • 元素位置改变。
  • 元素尺寸改变(margin、padding、边框厚度、width、height等)
  • 内容改变(文本改变或图片被另一个不同尺寸的图片替换)
  • 页面渲染器初始化。
  • 浏览器窗口尺寸改变。
  • 滚动条出现会使整个页面重排。

# 渲染树变化的排队与刷新

大多数浏览器通过队列化修改并批量执行来优化重排过程。。获取布局信息的操作会导致队列刷新,以此来获取最新信息。

//getComputedStyle()(currentStyle in IE)
bodystyle.color='red'
tmp=computed.backgroundColor;
bodystyle.color='#fff'
tmp=computed.backgroundImage;
bodystyle.color='#000'
tmp=computed.backgroundSize;
1
2
3
4
5
6
7

以上代码每次获取样式属性都要刷新渲染队列并重排,重排了三次。可以改成下面这样只重排一次:

//getComputedStyle()(currentStyle in IE)
bodystyle.color='#fff'
bodystyle.color='#000'
bodystyle.color='red'
tmp=computed.backgroundImage;
tmp=computed.backgroundColor;
tmp=computed.backgroundSize;
1
2
3
4
5
6
7

为了减少发生次数,应该合并多次对DOM的样式的修改,然后一次处理掉。

//重排了三次
el.style.borderLeft='1px'
el.style.borderRight='1px'
el.style.padding='1px'
//重排了一次
el.style.cssText='border-left:1px;border-right:1px;'
1
2
3
4
5
6

# 批量修改DOM

可以通过以下步骤来减少重绘和重排的次数:

  1. 使文档脱离文档流。
  2. 对其应用多重改变。
  3. 把元素带回到文档中。

该过程会触发两次重排。

  1. 通过改变display属性临时从文档中移除元素,改变完后,再恢复它。
  2. 在文档之外创建并更新一个文档片断(fragment),然后把它附加到原始列表中。文档片断是一个轻量级的document对象。文档片断一个便利的语法特性是当你附加一个片断到节点中时,实际上被添加的是该片断的子节点,而不是片断本身。(推荐方案,因为DOM遍历最少,重排次数最少)
var fragment=document.createDocumentFragment()
appendDatatoElement(fragment,data)
el.appendChild(fragment)
//以上代码只触发了一次重排
1
2
3
4

3.为需要修改的节点创建一个备份,然后对副本进行操作,完成后使用新节点替换旧的节点。(el.cloneNode(true))

# 缓存布局信息

1.当你查询布局信息是,比如获取(offsets)、滚动位置、或计算出的样式值时,浏览器为了返回最新值,会刷新队列并应用所有改变。最好的做法是减少布局信息的获取次数,获取后把它赋值给局部变量,然后操作局部变量。

# 事件委托

  1. 当页面中存在大量元素,而且每一个都要一次或多次绑定事件处理器时,这种情况可能会影响性能。
  2. 可以使用事件委托。:事件逐层冒泡并能被父级元素捕获。使用事件代理,只需要给外层元素绑定一个处理器,就可以处理在其子元素上触发的所有事件。
  3. 根据DOM标准每个事件都要经历三个阶段:
  • 捕获
  • 到达目标
  • 冒泡
    IE不支持捕获。

可以根据e.target判断是哪个元素。

e.preventDafault();//阻止默认行为
e.stopPropagation();//取消冒泡
1
2