《高性能javascript》读书笔记:第三章 DOM编程
文档对象模型(DOM)用于操作XML和HTML文档的应用程序接口,与语言无关。
各浏览器中DOM和javascript独立实现,如表
浏览器 | DOM | javascript |
IE | Trident(mshtml.dll) | JScript(jscript.dll) |
Safari | Webkit中的WebCore | JavaScriptCore(最新版叫SquirrelFish) |
Webkit中的WebCore | V8 | |
Firefox | Gecko | SpiderMonkey(最新版叫TraceMonkey) |
DOM和javascript(ECMAScript)通过接口连接,所以访问和修改DOM会影响性能。
innerHTML:
document.getElementById("aaa").innerHTML="<table></table>"
DOM:
var tr = document.CreateElement("tr");
var td=document.CreateElement("td");
td.appendChild(document.createTextNode('aaa'));
tr.appendChild(td);
td=document.createElement("td");
var a=document.createElement("a");
a.setAttribute("href","http://baidu.com");
td.appendChild(a);
tr.appendChild(td);
上面两种方式生成html的性能比较,innerHTML的优势在老版本浏览器中很明显,只有基于WebKit内核的新版浏览器中DOM方法略胜。
更新一大段HTML推荐使用innerHTML。
创建重复的元素时,第一个用document.createElement,后面的用 tr.cloneNode(false)速度会略快一点点。
HTML集合包括:
document.getElementsByName();
document.getElementsByClassName();
document.getElementsByTagName();
document.images
document.links
document.forms
document.forms[0].elements
html集合是类似数组的列表(没有push()或slice()之类的方法所以不是数组)。但是能以数字索引的方式访问列表中的元素,还有length属性。
html集合非常低效,因为它是实时和文档保持一致的,包括访问length属性都会重复执行查询的过程。
比如下面的就是一个死循环
var alldivs=document.getElementsByTagName("div");
for(var i=0;i<alldivs.length;i++){ //因为每次执行了appendChild后这个length都加了1,所以是个死循环。并且访问length属性性能也很低。
document.body.appendChild(document.createElement("div");
}
一般解决上面性能慢的方式,是设置一个集合,并把它拷贝到一个数组中,下面的可为通用转换函数:
function toArray(coll){
for(var i=0, a=[],len=coll.length;i<len;i++){
a[i]=coll[i];
}
}
在DOM中爬行,也就是从某个DOM元素开始操作周围的元素,或者递归查找所有的子节点,建议使用nextSibling,别用childNodes
下面表格中建议用第一列的DOM属性,
性能好的属性名 | 被替换的属性名 |
children | childNodes |
childElementCount | childNodes.length |
firstElementChild | firstChild |
lastElementChild | lastChild |
nextElementSibling | nextSibling |
previousElementSibling | previousSibling |
querySelectorAll方法:可进行组合查询。此种方法反回的是数组对象,不是html集合,因此返回的节点不会对应实时的文档结构,避免了html集合引起的性能问题。支持的浏览器有IE8,Firefox3.5,Safari3.1,Chrome1,Opera.
var elements = document.querySelectorAll("#menu a");
浏览器下载完页面以后生成两个结构 DOM树和渲染树(渲染树中的节点称帧frames或盒boxes)
按css模型的定义,页面元素为具有填充(padding),边距(margins),边框(borders)和位置(position)的盒子。
浏览器显示的顺序:下载页面--构建DOM树和渲染树--显示(绘制paint)页面元素
当DOM的变化影响了元素的几何属性(宽和高),浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树,即重排reflow,重排后会重新绘制受影响部分到屏幕中,即重绘repaint
重排影响性能。重排在下面情况时会发生:添加或删除可见的DOM元素,元素位置改变,元素尺寸改变,内容改变,页面渲染器初始化,浏览器窗口尺寸改变。
浏览器通过队列化优化重排。但是下面的属性和方法需要返回最新的布局信息,所以会触发重排。
offsetTop,offsetLeft,offsetWidth,offsetHeigth
scrollTop,scrollLeft,scrollWidth,scrollHeigth
clientTop,clientLeft,clientWidth,clientHeigth
getComputedStyle() //这个是在IE、Opera中
在修改样式的过程中,最好避免使用上面列出的属性。
减少能引起重排的操作,合并所有的改变最后一次处理,可使用cssText属性实现或改变css的class名称实现:
var el=document.getElementById("mydiv");
el.style.cssText="border-left:1px;border-right:2px;padding:5px;";
el.className="active";//改变类时需要检查级联样式,有轻微的性能影响
排量修改DOM的三种方案(推荐第二种) :目的是减少改变DOM的次数提高性能
1,通过改变display属性,临时从文档中移除,做了所有的修改以后再恢复。这样最多只会更改DOM两次。
var ul=document.getElementById("mylist");
ul.style.display="none";
//这里可进行对mylist的任意多次修改
ul.style.display="block";
2,在文档之外更新一个文档片断,然后把它附加到原始列表中。只触发一次重排。
var fragment=document.createDocumentFragment();
//这里可进行任意修改
document.getElementById("mylist").appendChild(fragment);//附加一个片断到节点中时,实际上被添加的是该片断的子结点。
3,为需要修改的节点创建一个备份,修改完后再替代旧的节点。
var old=document.getElementById("mylist");
var clone=old.cloneNode(true);
//修改
old.parentNode.replaceChild(clone,old);
少使用:hover这个CSS伪选择器。特别是在IE8中最影响性能。
页面中存在大量元素,并且其中很多元素都需要绑定事件处理器(onclick等),可能会影响性能。可以在用冒泡方式在父级元素捕获。
document.getElementById("parent").onclick=function(e){//假设parent是父元素,它含有很多子元素。
//浏览器target
e=e||window.event;
var target =e.target||e.srcElement;
var pageid,hrefparts;
//只关心hrefs,非链接点击则退出。这个示例中只演示捕获parent中子节点是A标签的情况
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;
}
};