用JS进行DOM操作的代价是昂贵的,它是富web应用中最常见的性能瓶颈。
文档对象模型(DOM)是一个独立于语言的,用于操作XML和HTML文档的程序接口(API)。通常在浏览器中DOM和JS都是独立的,由于彼此独立,所以JS操作DOM,性能开销就很大。
//循环操作DOM15000次function innerHTMLLoop(){ for(var count = 0; count < 15000; count++){ document.getElementById('here').innerHTML += 'a'; }}//只操作1次DOM,在IE8中性能提升273倍function innerHTMLLoop(){ var content = ''; for(var count = 0; count < 15000; count++){ content += 'a'; } document.getElementById('here').innerHTML = content;}
var newDiv = "<div></div>";document.getElementById('here').innerHTML = newDiv;//var newElement = document.createElement('div');document.getElementById('here').appendChild(newElement);
var newElement1 = document.createElement('div');var newElementN = newElement1.cloneNode(true);
//因为每次增加div后,长度添加,此循环是死循环var divList= document.getElementByName('div');for(var count = 0; count < divList.length; count++){ document.body.appendChild(document.createElement('div'));}//集合保存到变量,避免重复查询for(var count = 0; count < document.getElementByName('div').length; count++){ //do something}var divList= document.getElementByName('div');var len = divList.lengthvar arr = toArray(divList); //toArray是自己设置的函数,将集合转成arrayfor(var count = 0; count < len; count++){ //do something}
注意:此方法会额外添加一次遍历操作,长度小的集合可能不会提升反而下降
for(var i = 0; i < document.getElementsByTagName("a").length; i++){ document.getElementsByTagName("a")[i].class = 'active'}//改进后var list = document.getElementsByTagName("a");var len = list.length;for(var i = 0; i < len; i++){ list[i].class = 'active'}
元素节点属性名 | 被替代的属性名 |
---|---|
children | childNodes |
childElementCount | childNodes.length |
firstElementChild | firstChild |
lastElementChild | lastChild |
nextElementSibling | nextSibling |
previousElementSibling | previousSibling |
//改进前var els = document.getElementsById("menu").getElementsByTagName("a");//改进后var els = document.querySelectorAll("#menu a");
假如是组合查询,querySelectorAll()方法更具优势。比照一下:
//改进前var els = [];var divs = document.getElementsByTagName("div");var className = "";for(var i = 0,len = divs.length; i<len;i++){ className = divs[i].className; if(className === 'warning' || className === 'notice'){ els.push(divs[i]); }}//改进后var els = document.querySelectorAll("div.warning, div.notice");
推荐使用querySelector()方法,查询第一个匹配的节点。
浏览器下载完所有的组件文件(html、js、css、图片)之后,会解析这些组件,并生成两个数据结构:
因为重排消耗大,大多数浏览器都会通过队列化修改并批量执行来优化重排过程。获取布局的如下操作会导致队列刷新:
为减少重排或者者重绘,应该合并屡次对DOM和样式的修改,而后一次性解决。
//优化前,执行了三次重排。大部分现代浏览器进行了优化,可能只执行一次var el = document.getElementById('mydiv');el.style.borderLeft='1px';el.style.borderRight='2px';el.style.padding='5px';//优化后,只执行一次var el = document.getElementById('mydiv');el.style.ccsText='border-left:1px;border-right:2px;padding:5px;';//第二种优化方法var el = document.getElementById('mydiv');el.className='active';
可以通过如下步骤减少重绘和重排次数:
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); appendToElement.appendChild(li); }};//优化前,循环内N次重排var ul = document.getElementById('mylist');appendDataToElement(ul, data);//第一种,异常和显示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);
推荐第二种方案,其产生的DOM遍历和重排次数最少。
当查询布局信息(例如offsets,scroll,client等),浏览器为返回最新值,会刷新队列并应用所有变更。所哟尽量减少布局信息的获取次数,获取后赋值给局部变量,而后再操作局部变量。
例如移动元素的例子,timeout循环部分
//改进前myElement.style.left = 1 + myElement.offsetLeft + 'px';myElement.style.top = 1 + myElement.offsetTop + 'px';if (myElement.offsetLeft >= 500) { stopAnimation();}//改进后,先一次性获取初始位置var current = myElement.offsetLeft;//而后循环执行操作current++myElement.style.left = current + 'px';myElement.style.top = current + 'px';if (current >= 500) { stopAnimation();}
采用绝对位置定位,可以减少元素尺寸变化时,对其余元素造成的重排影响。
例如折叠/开展这种交互方式,每次变化都会导致下方所有元素的移动。假如把这部分元素使用绝对位置定位,覆盖其余部分。这样就能避免下方元素的重排和重绘,减少开销。
从IE7开始,IE允许任何元素上使用:hover这个CSS伪选择器。假如大量使用:hover,响应速度下降显著。特别是IE8。
假如进行大量的DOM元素事件绑定,会引入性能问题。一个简单的处理方案是事件委托。只要给最外层的元素绑定事件,利用事件逐层冒泡并能被父级元素捕获,即可以解决所有子元素上触发的事件。