高性能Javascript(2) DOM編程

第三部分 DOM編程

 

文檔對象模型(DOM)是一個獨立於語言的,使用XML和HTML文檔操做的應用程序接口(API)。在瀏覽器中,主要與HTML文檔打交道,在網頁應用中檢索XML文檔也很常見。DOM APIs主要用於訪問這些文檔中的數據。儘管DOM是與語言無關的API,在瀏覽器中的接口倒是以JavaScript實現的。客戶端大多數腳本程序與文檔打交道,DOM就成爲JavaScript代碼平常行爲中重要的組成部分。php

 

2.1 DOM Access and Modification  DOM訪問和修改

簡單來講,正如前面所討論的那樣,訪問一個DOM元素的代價就是交一次「過橋費」。修改元素的費用可能更貴,由於它常常致使瀏覽器從新計算頁面的幾何變化.以下面的例子:html

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

 此函數在循環中更新頁面內容。這段代碼的問題是,在每次循環單元中都對DOM元素訪問兩次:一次讀取innerHTML屬性能容,另外一次寫入它。所以,快捷的方法是生成完整的字符串之後一次性的對DOM元素進行修改和訪問。node

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

 這些結果清楚地代表,訪問DOM越多,代碼的執行速度就越慢。所以,通常經驗法則是:輕輕地觸摸DOM,並儘可能保持在ECMAScript範圍內。web

2.2 innerHTML Versus DOM methods  innerHTML與DOM方法比較編程

多年來,在web開發者社區已經對此問題進行了許多討論:更新頁面時,使用雖不標準卻被良好支持的innerHTML屬性更好呢,仍是使用純DOM方法,document.createElement ()更好呢?若是不考慮標準問題,它們的性能如何?答案是:性能差異不大,可是,在全部瀏覽器中,innerHTML速度更快一些,除了最新的基於WebKit的瀏覽器。數組

 

function tableInnerHTML() { 
 var i, h = ['<table border="1" width="100%">']; 
 h.push('<thead>'); 
 h.push('<tr><th>id<\/th><th>yes?<\/th><th>name<\/th><th>url<\/th><th>action<\/th><\/tr>'); 
 h.push('<\/thead>'); 
 h.push('<tbody>'); 
 for (i = 1; i <= 1000; i++) { 
 h.push('<tr><td>'); 
 h.push(i); 
 h.push('<\/td><td>'); 
 h.push('And the answer is... ' + (i % 2 ? 'yes' : 'no')); 
 h.push('<\/td><td>'); 
 h.push('my name is #' + i); 
 h.push('<\/td><td>'); 
 h.push('<a href="http://example.org/' + i + '.html">http://example.org/' + i + '.html<\/a>'); 
 h.push('<\/td><td>'); 
 h.push('<ul>'); 
 h.push(' <li><a href="edit.php?id=' + i + '">edit<\/a><\/li>'); 
 h.push(' <li><a href="delete.php?id="' + i + '-id001">delete<\/a><\/li>'); 
 h.push('<\/ul>'); 
 h.push('<\/td>'); 
 h.push('<\/tr>'); 
 } 
 h.push('<\/tbody>'); 
 h.push('<\/table>'); 
 document.getElementById('here').innerHTML = h.join(''); 
};

 

 上面的例子描述了用innerHTML進行生成元素的方式。咱們在這裏只是記住這幾個基本方式就能夠了。瀏覽器

 2.3 Cloning Nodes 節點克隆

 

  2.4 HTML Collections  HTML集合

 HTML集合是用於存放DOM節點引用的類數組對象。下列函數的返回值就是一個集合:
     • document.getElementsByName()
     • document.getElementsByClassName()
     • document.getElementsByTagName_r()緩存

下列屬性也屬於HTML集合:函數

  • document.images頁面中全部的<img>元素
  • document.links 全部的<a>元素

 

  • document.forms 全部表單

 

  • document.forms[0].elements頁面中第一個表單的全部字段

這些方法和屬性返回HTMLCollection對象,是一種相似數組的列表。它們不是數組(由於它們沒有諸如push()或slice()之類的方法),可是提供了一個length屬性,和數組同樣你可使用索引訪問列表中的元素。oop

2.4 Expensive collections 昂貴的集合

正如在第四章中將要討論的,不建議用數組的length屬性作循環判斷條件。訪問集合的length比數組的length還要慢,由於它意味着每次都要從新運行查詢過程.

在下面的例子中,在循環中訪問每一個元素的三個屬性。最慢的版本每次都要訪問全局的document,優化後的版本緩存了一個指向集合的引用,最快的版本將集合的當前元素存入局部變量。全部三個版本都緩存了集合的length屬性。

 

// slow 
function 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; 
};

 

// faster 
function 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; 
}; 
// fastest 
function 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; 
};

 關於DOM的操做可提升性能的地方有不少,例如訪問兄弟節點和子節點等等。

2.5 The Selectors API 選擇器API

識別DOM中的元素時,開發者常常須要更精細的控制,而不只是getElementById()和getElementsByTagName_r()之類的函數。有時你結合這些函數調用並迭代操做它們返回的節點,以獲取所須要的元素,這一精細的過程可能形成效率低下.另外一方面,使用CSS選擇器是一個便捷的肯定節點的方法,由於開發者已經對CSS很熟悉了。許多
JavaScript庫爲此提供了API,並且最新的瀏覽器提供了一個名爲querySelectorAll()的原生瀏覽器DOM函數。顯然這種方法比使用JavaScript和DOM迭代並縮小元素列表的方法要快。以下所示:

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

elements的值將包含一個引用列表,指向那些具備id="menu"屬性的元素。函數querySelectorAll()接收一個CSS選擇器字符串參數並返回一個NodeList——由符合條件的節點構成的類數組對象。此函數不返回HTML集合,因此返回的節點不呈現文檔的「存在性結構」。這就避免了本章前面提到的HTML集合所固有的性能問題(以及潛在的邏輯問題)。

若是不使用querySelectorAll(),達到一樣的目標的代碼會冗長一些:
var elements = document.getElementById('menu').getElementsByTagName_r('a');

這種狀況下elements將是一個HTML集合,因此你還須要將它拷貝到一個數組中,若是你想獲得與
querySelectorAll()一樣的返回值類型的話

Summary

 DOM訪問和操做是現代網頁應用中很重要的一部分。但每次你經過橋樑從ECMAScript島到達DOM島
時,都會被收取「過橋費」。爲減小DOM編程中的性能損失,請牢記如下幾點:


• Minimize DOM access, and try to work as much as possible in JavaScript.
最小化DOM訪問,在JavaScript端作儘量多的事情。
• Use local variables to store DOM references you'll access repeatedly.
在反覆訪問的地方使用局部變量存放DOM引用.
• Be careful when dealing with HTMLcollections because theyrepresent the live, underlying document. Cache
the collection lengthinto a variable and use it when iterating, and make a copy of the collection into an array for
heavy work on collections.
當心地處理HTML集合,由於他們表現出「存在性」,老是對底層文檔從新查詢。將集合的length屬性緩
存到一個變量中,在迭代中使用這個變量。若是常常操做這個集合,能夠將集合拷貝到數組中。
• Use faster APIs when available, such as querySelectorAll() and firstElementChild.
若是可能的話,使用速度更快的API,諸如querySelectorAll()和firstElementChild。
• Be mindful of repaints and reflows; batch style changes, manipulate the DOM tree "offline," and cache and
minimize access to layout information.
注意重繪和重排版;批量修改風格,離線操做DOM樹,緩存並減小對佈局信息的訪問。
• Position absolutely during animations, and use drag and drop proxies.

動畫中使用絕對座標,使用拖放代理。• Use event delegation to minimize the number of event handlers. 使用事件託管技術最小化事件句柄數量

相關文章
相關標籤/搜索