對Dom的訪問代價是昂貴,在富網頁應用中通常是性能的瓶頸,所以對Dom的優化十分重要。
一.訪問和修改Dom元素
浏覽器通常要求JavaScript和Dom實現保持獨立的。例如,在Internet Explorer 中,被稱為JScript的JavaScript 實現位於庫文件jscript.dll 中,而DOM 實現位於另一個庫mshtml.dll(內部代號Trident)。所以用JavaScript訪問Dom元素是需要一定的代價。人們通常這樣形容Dom訪問,javaScript和Dom相當於兩個獨立的小島,這兩個小島中間有座橋連起來,JavaScript每次訪問Dom都要過一次橋,過橋就要收取一定的過橋費。所以要盡量減少訪問Dom的次數,那麼如何做到呢?請看下面的例子。
function innerHTMLLoop() {
for (var count = 0; count < 15000; count++) {
document.getElementById('here').innerHTML += 'a';
}
}
上面這個函數的作用是對id是here這個Dom節點,進行了15000次修改,在這個函數中,對Dom節點進行了15000次訪問。再看下面的函數。
function innerHTMLLoop() {
var text="";
for (var count = 0; count < 15000; count++) {
text+= 'a';
}
document.getElementById('here').innerHTML=text;
}
上面這個函數和第一個函數實現的是完全一樣的功能,但是它對Dom的訪問只有一次,性能是大大提高。
現在來比較一下document.createElement和innerHTML。
document.createElement是每次創建一個節點,然後加入到父節點中。innerHTML是用字符串拼接成一系列Dom節點然後在插入到目標節點中。如果在一個性能苛刻的操作中更新一大塊HTML 頁面,innerHTML 在大多數浏覽器中執行更快。但對於大多數日常操作而言,其差異並不大,所以你應當根據代碼可讀性,可維護性,團隊習慣,代碼風格來綜合決定采用哪種方法。
另外HTML集合用於存放Dom節點類數組對象,例如document.getElementsByTagName,getElementsByClass等函數得到的就是集合。它的使用也是非常昂貴的。考慮下面的例子
var alldivs = document.getElementsByTagName_r('div');
for (var i = 0; i < alldivs.length; i++) {
document.body.appendChild(document.createElement('div'))
}
上面的例子表面上是想倍增div的數量,但實際上是個死循環,因為div的長度是動態變化的,也就是說alldivs.length不是一個常量,它會每次根據div的數量變化,這樣也就是每次要訪問Dom節點,我們之前說過訪問Dom的代價是昂貴的。所以最好的方法是將length用一個變量緩存起來,例如len=alldivs.length。
二.重繪和重排
我們都知道在頁面剛加載的時候,浏覽器會進行頁面渲染,其實這就是一次繪制和排列的過程。繪制的是頁面,排列的是Dom節點,一般來說繪制是根據color,background,opacity等css屬性,而排列則根據width,height,margin,padding等能影響頁面Dom元素位置變換的css屬性。那麼既然知道了什麼是重繪和重排,就應該知道什麼時候會重會和重排。也就是當你改變某些css屬性時。
一般來說重排的影響是遠大於重繪的,重排時浏覽器會重新計算所有元素的位置和尺寸,重新排列。這個過程可以說是相當耗時間的,所以要盡量避免重排。考慮下面例子
var el = document.getElementById('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
上面例子三次改變了el的css屬性,而這三次都產生的重排。再看下面代碼
var el = document.getElementById('mydiv');
el.style.cssText="border-left:1px;border-right:2px;padding:5px";
上面代碼實現的是和第一例子一樣的功能,但是它只產生的一次重排,大大提高了性能。
三.事件托管
當頁面中存在大量元素,而且每個元素有一個或多個事件句柄與之掛接(例如onclick)時,可能會影響性能。連接每個句柄都是有代價的,無論其形式是加重了頁面負擔(更多的頁面標記和JavaScript 代碼)還是表現在運行期的運行時間上。所以事件托管是很有用的。
每個事件發生時會經歷捕獲,到達目標,冒泡,三個階段。例如:
當用戶點擊了“menu #1”鏈接,點擊事件首先被<a>元素收到。然後它沿著DOM 樹冒泡,被<li>元素收到,然後是<ul>,接著是<div>,等等,一直到達文檔的頂層,甚至window。如果需要在每個li上加監聽事件,那麼可以加到ul上,因為每次點擊li都會冒泡到ul上。
總結:
DOM 訪問和操作是現代網頁應用中很重要的一部分。但每次你通過橋梁從ECMAScript 島到達DOM 島時,都會被收取“過橋費”。為減少DOM 編程中的性能損失,請牢記以下幾點:
最小化DOM 訪問,在JavaScript 端做盡可能多的事情
在反復訪問的地方使用局部變量存放DOM 引用.
小心地處理HTML 集合,因為他們表現出“存在性”,總是對底層文檔重新查詢。將集合的length 屬性緩存到一個變量中,在迭代中使用這個變量。如果經常操作這個集合,可以將集合拷貝到數組中。
注意重繪和重排版;批量修改風格,離線操作DOM 樹,緩存並減少對布局信息的訪問。
使用事件托管技術最小化事件句柄數量。