首页 > 代码库 > 高性能javascript(第三章 DOM编程)

高性能javascript(第三章 DOM编程)

1、浏览器之DOM

浏览器通常要求 DOM 实现和 JavaScript 实现保持相互独立

这对性能意味着什么呢?简单说来,两个独立的部分以功能接口连接就会带来性能损耗。一个很形象的 比喻是把 DOM 看成一个岛屿,把 JavaScript(ECMAScript)看成另一个岛屿,两者之间以一座收费桥连接

每次 ECMAScript 需要访问DOM 时,你需要过桥,交一次“过桥费”。你操作 DOM 次数越多,费用就越高。一般的建议是尽量减少过桥次数,努力停留在 ECMAScript 岛上。本章将对此问题给出详细解答,告诉你应该关注什么地方, 以提高用户交互速度。

 

2、DOM访问和修改

  访问一个 DOM 元素的代价就是交一次“过桥费”。修改元素的费用可能更贵,因为它经常导致浏览器重新计算页面的几何变化

function innerHTMLLoop() {for (var count = 0; count < 15000; count++) {document.getElementById(‘here‘).innerHTML += ‘a‘;

  上面这段代码对dom访问了两次一次读取它,另外一次写入它

  改进:

  

function innerHTMLLoop2() {    var content = ‘‘;    for (var count = 0; count < 15000; count++) {        content += ‘a‘;    }    document.getElementById(‘here‘).innerHTML += content;}

  一般经验法则是:轻轻地触摸 DOM,并尽量保持在 ECMAScript 范围内。

 

3、innerHTML 与 DOM 方法比较

  使用innerHTML创建1000*5的表:

  使用 DOM 方法创建同样的表:

  其实这两种方式在新版的浏览器上差别不是很大

 

4、使用节点克隆,一般会快2%~10%;

5、HTML集合:

  死循序:

    var alldivs = document.getElementsByTagName_r(‘div‘);    for (var i = 0; i < alldivs.length; i++) {        document.body.appendChild(document.createElement(‘div‘))    }

  看下面三种不同的访问方式:

    function toArray(coll) {        for (var i = 0, a = [], len = coll.length; i < len; i++) {            a[i] = coll[i];        }        return a;    }    var coll = document.getElementsByTagName_r(‘div‘);    var arr = toArray(coll);//slower    function loopCollection() {        for (var count = 0; count < coll.length; count++) {        }    }// faster    function loopCopiedArray() {        for (var count = 0; count < arr.length; count++) {        }    }//缓存  跟loopCopiedArray差不多一样快    function loopCacheLengthCollection() {        var coll = document.getElementsByTagName_r(‘div‘), len = coll.length;        for (var count = 0; count < len; count++) {        }    }

6、访问集合元素时使用局部变量(缓存):

  三种缓存方式的比较如下代码:

// slowfunction collectionGlobal() {    var coll = document.getElementsByTagName_r(‘div‘), len = coll.length,        name = ‘‘;    for (var count = 0; count < len; count++) {        name = document.getElementsByTagName_r(‘div‘)[count].nodeName;        name = document.getElementsByTagName_r(‘div‘)[count].nodeType;        name = document.getElementsByTagName_r(‘div‘)[count].tagName;    }    return name;};// fasterfunction collectionLocal() {    var coll = document.getElementsByTagName_r(‘div‘), len = coll.length,        name = ‘‘;    for (var count = 0; count < len; count++) {        name = coll[count].nodeName;        name = coll[count].nodeType;        name = coll[count].tagName;    }    return name;};// fastestfunction collectionNodesLocal() {    var coll = document.getElementsByTagName_r(‘div‘), len = coll.length,        name = ‘‘,        el = null;    for (var count = 0; count < len; count++) {        el = coll[count];        name = el.nodeName;        name = el.nodeType;        name = el.tagName;    }    return name;};
7、遍历dom tree:使用childNode集合或使用nextSibling两种方式的比较:
  在不同的浏览器上,这两种方式的运行时间基本相同:
//nextSibling方式function testNextSibling() {    var el = document.getElementById(‘mydiv‘), ch = el.firstChild,        name = ‘‘;    do {        name = ch.nodeName;    } while (ch = ch.nextSibling);    return name;};// nodeName方式function testChildNodes() {    var el = document.getElementById(‘mydiv‘), ch = el.childNodes,        len = ch.length,        name = ‘‘;    for (var count = 0; count < len; count++) {        name = ch[count].nodeName;    }    return name;};

 

8、只表示元素节点的DOM属性(HTML标签)和表示所有节点的属性如下表:一般情况下只表示元素节点的dom要快些

 

9、选择器API

var elements = document.querySelectorAll(‘#menu a‘);

  elements 的值将包含一个引用列表,指向那些具有 id="menu"属性的元素。函数 querySelectorAll()接收 一个 CSS 选择器字符串参数并返回一个 NodeList——由符合条件的节点构成的类数组对象。此函数不返回 HTML 集合,所以返回的节点不呈现文档的“存在性结构”。

  这就避免了本章前面提到的 HTML 集合所固有 的性能问题(以及潜在的逻辑问题)。

 

var elements = document.getElementById(‘menu‘).getElementsByTagName_r(‘a‘);

  这种情况下 elements 将是一个 HTML 集合,所以你还需要将它拷贝到一个数组中,如果你想得到与 querySelectorAll()同样的返回值类型的话。

 

对于下面两段代码,使用选择器API比对手快2~6倍

//使用选择器APIvar errs = document.querySelectorAll(‘div.warning, div.notice‘);//使用一般方法var errs = [],    divs = document.getElementsByTagName_r(‘div‘),    classname = ‘‘;for (var i = 0, len = divs.length; i < len; i++) {    classname = divs[i].className;    if (classname === ‘notice‘ || classname === ‘warning‘) {        errs.push(divs[i]);    }}

 


 

10、重绘和重排版

当浏览器下载完所有页面 HTML 标记,JavaScript,CSS,图片之后,它解析文件并创建两个内部数据结构:

   一棵DOM树:表示页面结构

   一棵渲染树:表示 DOM 节点如何显示

渲染树中为每个需要显示的 DOM 树节点存放至少一个节点(隐藏 DOM 元素在渲染树中没有对应节点)。渲染树上的节点称为“框”或者“盒”,符合 CSS 模型的定义,将页面元素看作一个具有填充、边距、 边框和位置的盒。

一旦 DOM 树和渲染树构造完毕,浏览器就可以显示(绘制)页面上的元素了。

 

当 DOM 改变影响到元素的几何属性(宽和高)——例如改变了边框宽度或在段落中添加文字,将发生一系列后续动作——浏览器需要重新计算元素的几何属性,而且其他元素的几何属性和位置也会因此改变受到影响。

浏览器使渲染树上受到影响的部分失效,然后重构渲染树。这个过程被称作重排版。重排版完成时,浏览器在一个重绘进程中重新绘制屏幕上受影响的部分。

不是所有的 DOM 改变都会影响几何属性。例如,改变一个元素的背景颜色不会影响它的宽度或高度。 在这种情况下,只需要重绘(不需要重排版),因为元素的布局没有改变。

 

 

 

 

 

高性能javascript(第三章 DOM编程)