《高性能javascript》读书笔记:第三章 DOM编程

 

文档对象模型(DOM)用于操作XML和HTML文档的应用程序接口,与语言无关。

各浏览器中DOM和javascript独立实现,如表

浏览器 DOM javascript
IE Trident(mshtml.dll) JScript(jscript.dll)
Safari Webkit中的WebCore JavaScriptCore(最新版叫SquirrelFish)
Google 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;

  }

};



 

 



posted on 2012-01-10 23:15  LCM  阅读(569)  评论(0编辑  收藏  举报

导航