文檔對象模型(DOM)是一個獨立於語言的,使用XML和HTML文檔操做的應用程序接口(API)。在瀏覽器中,主要與HTML文檔打交道,在網頁應用中檢索XML文檔也很常見。DOM APIs主要用於訪問這些文檔中的數據。儘管DOM是與語言無關的API,在瀏覽器中的接口倒是以JavaScript實現的。客戶端大多數腳本程序與文檔打交道,DOM就成爲JavaScript代碼平常行爲中重要的組成部分。php
簡單來講,正如前面所討論的那樣,訪問一個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進行生成元素的方式。咱們在這裏只是記住這幾個基本方式就能夠了。瀏覽器
HTML集合是用於存放DOM節點引用的類數組對象。下列函數的返回值就是一個集合:
• document.getElementsByName()
• document.getElementsByClassName()
• document.getElementsByTagName_r()緩存
下列屬性也屬於HTML集合:函數
這些方法和屬性返回HTMLCollection對象,是一種相似數組的列表。它們不是數組(由於它們沒有諸如push()或slice()之類的方法),可是提供了一個length屬性,和數組同樣你可使用索引訪問列表中的元素。oop
正如在第四章中將要討論的,不建議用數組的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的操做可提升性能的地方有不少,例如訪問兄弟節點和子節點等等。
識別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()一樣的返回值類型的話
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. 使用事件託管技術最小化事件句柄數量