DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript對DOM進行操作的指導性原則
JavaScript對DOM進行操作的指導性原則
編輯:關於JavaScript     

網頁制作poluoluo文章簡介:在過去的幾周中,我為大家介紹了幾種可以加快JavaScript腳本運行速度的技術。第一節 介紹了如何優化循環。第二節 的重點放在優化函數內部代碼上,還介紹了隊列(queuing)和記憶化(memoization)兩種技術,來減輕函數的工作負擔。第三節 就如何將遞歸轉換為迭代循環或

在Web開發中,JavaScript的一個很重要的作用就是對DOM進行操作,可你知道麼?對DOM的操作是非常昂貴的,因為這會導致浏覽器執行回流操作,而執行了過多的回流操作,你就會發現自己的網站變得越來越慢了,我們應該盡可能的減少DOM操作。本文是這個系列的最後一篇,給出了一些指導性原則,比如在什麼時候應該對DOM可以進行什麼樣的操作等。

【原文】Nicholas C. Zakas - Speed up your JavaScript, Part 4
【譯文】明達 - 如何提升JavaScript的運行速度(DOM篇)

以下是對原文的翻譯

在過去的幾周中,我為大家介紹了幾種可以加快JavaScript腳本運行速度的技術。第一節 介紹了如何優化循環。第二節 的重點放在優化函數內部代碼上,還介紹了隊列(queuing)和記憶化(memoization)兩種技術,來減輕函數的工作負擔。第三節 就如何將遞歸轉換為迭代循環或者記憶化方式的話題,展開了討論。第四節是這個系列的最後一篇,也就是本文,將重點闡述過多的DOM操作所帶來的影響。

我們都知道,DOM操作的效率是很低的,而且不是一般的慢,而且這也是引發性能問題的常見問題之一。為什麼會慢呢?因為對DOM的修改為影響網頁的用戶界面,重繪頁面是一項昂貴的操作。太多的DOM操作會導致一系列的重繪操作,為了確保執行結果的准確性,所有的修改操作是按順序同步執行的。我們稱這個過程叫做回流(reflow),同時這也是最昂貴的浏覽器操作之一。回流操作主要會發生在幾種情況下:

  • * 當對DOM節點執行新增或者刪除操作時。
  • * 動態設置一個樣式時(比如element.style.width="10px")。
  • * 當獲取一個必須經過計算的尺寸值時,比如訪問offsetWidth、clientHeight或者其他需要經過計算的CSS值(在兼容DOM的浏覽器中,可以通過getComputedStyle函數獲取;在IE中,可以通過currentStyle屬性獲取)。

解決問題的關鍵,就是限制通過DOM操作所引發回流的次數。大部分浏覽器都不會在JavaScript的執行過程中更新DOM。相應的,這些浏覽器將對對DOM的操作放進一個隊列,並在JavaScript腳本執行完畢以後按順序一次執行完畢。也就是說,在JavaScript執行的過程中,用戶不能和浏覽器進行互動,直到一個回流操作被執行。( 失控腳本對話框 會觸發回流操作,因為他執行了一個中止JavaScript執行的操作,此時會對用戶界面進行更新)

如果要減少由於DOM修改帶來的回流操作,有兩個基本的方法。第一個就是在對當前DOM進行操作之前,盡可能多的做一些准備工作。一個經典的例子就是向document對象中添加很多DOM節點:

for (var i=0; i < items.length; i++){
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Option " + i);
    list.appendChild(item);
}

這段代碼的效率是很低的,因為他在每次循環中都會修改當前DOM結構。為了提高性能,我們需要將這個次數降到最低,對於這個案例來說,最好的辦法是建立一個文檔碎片(document fragment),作為那些已創建元素元素的臨時容器,最後一次將容器的內容直接添加到父節點中:

var fragment = document.createDocumentFragment();
for (var i=0; i < items.length; i++){
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Option " + i);
    fragment.appendChild(item);
}
list.appendChild(fragment);

經過調整的代碼,只會修改一次當前DOM的結構,就在最後一行,而在這之前,我們用文檔碎片來保存那些中間結果。因為文檔碎片沒有任何可見內容,所以這類修改不會觸發回流操作。實際上,文檔碎片也不能被添加到DOM中,我們需要將它作為參數傳給appendChild函數,而實際上添加的不是文檔碎片本身,而是它下面的所有子元素。

網頁制作poluoluo文章簡介:在過去的幾周中,我為大家介紹了幾種可以加快JavaScript腳本運行速度的技術。第一節 介紹了如何優化循環。第二節 的重點放在優化函數內部代碼上,還介紹了隊列(queuing)和記憶化(memoization)兩種技術,來減輕函數的工作負擔。第三節 就如何將遞歸轉換為迭代循環或

避免不必要回流操作的另外一種方法,就是在對DOM操作之前,把要操作的元素,先從當前DOM結構中刪除。對於刪除一個元素,基本有兩種方法:

  1. 通過removeChild()或者replaceChild()實現真正意義上的刪除。
  2. 設置該元素的display樣式為“none”。

而一旦修改操作完成,上面這個過程就需要反轉過來,將刪除的元素重新添加到當前的DOM結構中,我們還是拿上面的例子來做說明:

list.style.display = "none";
for (var i=0; i < items.length; i++){
    var item = document.createElement("li");
    item.appendChild(document.createTextNode("Option " + i);
    list.appendChild(item);
}
list.style.display = "";

將list的display樣式設置為“none”後,就將這個元素從當前的DOM結構中刪除了,因為這個節點不再可視。在將display屬性設置回之前的默認值之前,向其下添加子元素是不會觸發回流操作的。

另外一個經常引起回流操作的情況是通過style屬性對元素的外觀進行修改。比如下面這個例子:

element.style.backgroundColor = "blue";
element.style.color = "red";
element.style.fontSize = "12em";

這段代碼修改了三個樣式,同時也就觸發了三次回流操作。每次修改元素的style屬性,都肯定會觸發回流操作。如果你要同時修改一個元素的很多樣式,最好的辦法是將這些樣式放到一個class下,然後直接修改元素的class,這可比單獨修改元素的樣式要強得多。比如下面這個例子:

.newStyle {
    background-color: blue;
    color: red;
    font-size: 12em;
}

這樣我們在JavaScript代碼中,只需下面這行代碼就可以修改樣式:

/*element.className = "newStyle";*/

修改元素的class屬性,會一次將所有的樣式應用在目標元素上,而且只會觸發一次回流操作。這樣做不止更加有效,而且還更容易維護。

既然DOM幾乎在所有情況下都很慢,就很有必要將獲取的DOM數據緩存起來。這種方法,不僅對獲取那些會觸發回流操作的屬性(比如offsetWidth等)尤為重要,就算對於一般情況,也同樣適用。下面介紹一個效率低的誇張的例子:

document.getElementById("myDiv").style.left = document.getElementById("myDiv").offsetLeft +
    document.getElementById("myDiv").offsetWidth + "px";

這裡對getElementById()調用了三次,是一個很大的問題,訪問DOM是很昂貴的,而這三個調用恰恰訪問的是同一個元素,也許我們像下面這樣寫,會更好一些:

var myDiv = document.getElementById("myDiv");
myDiv.style.left = myDiv.offsetLeft + myDiv.offsetWidth + "px";

我們去掉了一些冗余操作,現在對DOM操作的次數已經被減小了。對於那些使用次數超過一次的DOM值,我們都應該緩沖起來,這樣可以避免無謂的性能消耗。

也許,拖慢屬性訪問速度的罪魁禍首就是HTMLCollection對象。這些對象是object類型的,只要DOM需要返回一組節點時就會使用這個對象,也就是說childNodes屬性和getElementsByTagName()的返回值都屬於這種情況。我們可能經常會將HTMLCollection當作數組來使用,但實際上他是一個根據DOM結構自動變化的實體對象。每次你訪問一個HTMLCollection對象的屬性,他都會對DOM內所有的節點進行一次完整匹配,這意味著下面的代碼將導致一個死循環:

var divs = document.getElementsByTagName("div");
for (var i=0; i < divs.length; i++){  //infinite loop
    document.body.appendChild(document.createElement("div"));
}

這段代碼為什麼會變成死循環呢?因為在每次循環中,將會向document中新增一個div元素,同時也會更新divs這個集合,也就是說循環的索引永遠都不會超過divs.length的值,因為divs.length的值是伴隨著循環而遞增的。每次訪問divs.length,就會更新一次集合對象,這可比訪問一個普通數組的length屬性要付出更大的代價。當對HTMLCollection對象進行操作時,應該將訪問的次數盡可能的降至最低,最簡單的,你可以將length屬性緩存在一個本地變量中,這樣就能大幅度的提高循環的效率。

var divs = document.getElementsByTagName("div");
for (var i=0, len=divs.length; i < len; i++){  //not an infinite loop
    document.body.appendChild(document.createElement("div"));
}

修改後的代碼已經不是死循環了,因為在每次循環時,len的值都是保持固定不變的。將屬性值緩存起來除了更加有效率,還可以保證document不會執行多於一次的查詢。

本文是“Speed up your JavaScript”這個系列的最後一篇文章,我希望你現在已經知道如何避免那個腳本失控的對話框,以及如何讓你的腳本運行的更快。我所提到的技巧很多別人已經提過了,我只是將它們組織到一起,這樣大家可以更容易的找到這些信息。如果你有什麼更好的話題需要來我整理,在評論中直接告訴我,或者直接 聯系我 吧。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved