访问和操作DOM是现代Web应用的重要部分。但每次穿越连接ECMAScript和DOM两个岛屿之间的桥梁,都会被收取“过桥费”。为了减少DOM编程带来的性能损失。
- 最小化DOM访问次数,尽可能在JavaScript端处理。 (只适用于IE6之前的版本,新版本浏览器DOM方法更优)
//多次调用DOM的API,不妥
function innerHTMLLoop() {
for(var count = 0; count < 15000; count++) {
document.getElementById('here').innerHTML += 'a';
}
}
// 使用以下方法代替,只调用一次DOM的API,尽可能将在JavaScript端处理后再赋值
function innerHTMLLoop2() {
var content = '';
for(var count = 0; count < 15000; count++) {
content += 'a';
}
document.getElementById('here').innerHTML += content;
}
- 使用节点克隆element.cloneNode()(element表示已有节点)比document.createElement()稍快。
- 如果需要多次访问某个DOM节点,请使用局部变量存储它的引用。
- 小心处理HTML集合,因为它实时联系着底层文档。把集合的长度缓存到一个变量中,并在迭代中使用它。如果需要经常操作集合,建议把它拷贝到一个数组中。
以下方法返回的是一个集合:
- document.getElementsByName()
- document.getElementsByClassName()
- document.getElementsByTagName()
以下属性同样返回HTML集合
- document.images
- document.links
- document.forms
- document.forms[0].elements
//以下代码会造成死循环,因为每次访问alldivs.length时都是实时的DOM信息
var alldivs = document.getElementsByTagName('div');
for(var i = 0; i < alldivs.length; i++) {
document.body.appendChild(document.createElement('div'));
}
//将HTML集合拷贝到数组中使用
function toArray(coll) {
for(var i = 0,a = [],len = coll.length; i < len; i++) {
a[i] = coll[i];
}
return a;
}
//访问集合的length比普通数组的length慢很多,所以也可以将集合的长度保存到变量中,用于循环判定
function loopCacheLengthCollection() {
var coll = document.getElementsByTagName('div'),
len = coll.length;
for(var count = 0; count < len; count++) {
/* 代码处理 */
}
}
- 如果可能的话,使用速度更快的API,比如querySelectorAll()和firstElementChild。
在老版本IE中nextSibling比childNode快。
老API不区分元素节点和其他节点,比如注释和文本节点。但在某些情况下只需访问元素节点,所以使用以下新API代替旧API
新属性名 被代替的属性 children childNodes childElementCount childNodes.length firstElementChild firstChild lastElementChild lastChild nextElementSibling nextSibling previousElementSibling previousSibling
- 要留意重绘和重排;批量修改样式时,“离线“操作DOM树,使用缓存,并减少访问布局信息的次数。
改变页面布局和几何属性会触发重排。但是浏览器一般会通过队列化修改并批量执行来优化重排。但是获取布局信息会导致队列刷新,如offsetTop,scrollTop,cilentTop等
批量修改DOM的方法:
- 使文档脱离文档流。
- 对其应用多重改变。
- 把元素带回文档中。
三种方法:
- 隐藏元素,应用修改,重新显示。
- 使用文档片段(document fragment)在当前DOM之外构建一个子树,再把它拷贝回文档。
- 将原始元素拷贝到一个脱离文档的节点,修改副本,完成后再替换原始元素。
//更新指定节点数据的通用函数:
function appendDataToElement(appendToElement, data) {
var a, li;
for(var i = 0,max = data.length;i < max; i++) {
a = document.createElement('a');
a.href = data[i].url;
a.appendChild(document.createTextNode(data[i].name));
li = document.createElement('li');
li.appendChild(a);
appendDataToElement.appendChild(li);
}
}
//第一种方法
var ul = document.getElementById('mylist');
ul.style.display = 'none';
appendDataToElement(ul,data);
ul.style.display = 'block';
//第二种方法,文档片段只会添加其内容而不会添加其本身。
var fragment = document.createDocumentFragment();
appendDataToElement(fragment,data);
document.getElementById('mylist').appendChild(fragment);
//第三种方案
var old = document.getElementById('mylist');
var clone = old.cloneNode(true);
appendDataToElement(clone,data);
old.parentNode.replaceChild(clone,old);
- 动画中使用绝对定会,使用拖放代理。
使用以下步骤可以避免页面中的大部分重排:
- 使用绝对定位页面上的动画元素,将其脱离文档流。
- 让元素动起来。当它扩大时,会临时覆盖部分页面。但这只是页面一个小区域的重绘过程,不会产生重排并重绘页面大部分内容。
- 当动画结束时恢复定位,从而只会下移一次文档的其他元素。
- 使用事件委托来减少事件处理器的数量。
//对li的父标签ul统一绑定事件委托
document.getElementById('menu').onclick = function(e) {
// 浏览器target
e = e || window.events;
var target = e.target || e.srcElement;
var pageid,hrefparts;
//只关心hrefs,非链接点击则退出
if(target.nodeName !== 'A') {
return;
}
//从链接中找出页面ID
hrefparts = target.href.split('/');
pageid = hrefparts[hrefparts.length - 1];
pageid = pageid.replace('.html','');
//更新页面
ajaxRequest('xhr.php?page=' + id, updatePageContents);
//浏览器组织默认行为并取消冒泡
if(typeof e.preventDefault === 'function') {
e.preventDefault();
e.stopPropagation();
} else {
e.returnValue = false;
e.cancelBubble = true;
}
}