欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

JavaScript 高效運(yùn)行代碼分析

 更新時間:2010年03月18日 21:26:00   作者:  
傳統(tǒng)上,網(wǎng)頁中不會有大量的腳本,至少腳本很少會影響網(wǎng)頁的性能。

作者 Mark 'Tarquin' Wilton-Jones · 2006年11月2日

本文翻譯自 Efficient JavaScript

原譯文地址 http://kb.operachina.com/node/207

傳統(tǒng)上,網(wǎng)頁中不會有大量的腳本,至少腳本很少會影響網(wǎng)頁的性能。但隨著網(wǎng)頁越來越像 Web 應(yīng)用程序,腳本的效率對網(wǎng)頁性能影響越來越大。而且使用 Web 技術(shù)開發(fā)的應(yīng)用程序現(xiàn)在越來越多,因此提高腳本的性能變得很重要。

對于桌面應(yīng)用程序,通常使用編譯器將源代碼轉(zhuǎn)換為二進(jìn)制程序。編譯器可以花費(fèi)大量時間優(yōu)化最終二進(jìn)制程序的效率。Web 應(yīng)用程序則不同。因?yàn)閃eb應(yīng)用程序需要運(yùn)行在不同的瀏覽器、平臺和架構(gòu)中,不可能事先完全編譯。瀏覽器在獲得腳本后要執(zhí)行解釋和編譯工作。用戶要求不僅要求網(wǎng)頁能快速的載入,而且要求最終 Web 應(yīng)用程序執(zhí)行的效果要和桌面應(yīng)用程序的一樣流暢。Web 應(yīng)用程序應(yīng)能運(yùn)行在多種設(shè)備上,從普通的桌面電腦到手機(jī)。

瀏覽器并不很擅長此項(xiàng)工作。雖然 Opera 有著當(dāng)前最快的腳本引擎,但瀏覽器有不可避免的局限性,這時就需要 Web 開發(fā)者的幫助。Web開發(fā)者提高 Web 應(yīng)用程序的性能的方法很多而且也很簡單,如只需要將一種循環(huán)變成另一種、將組合樣式分解成三個或者只添加實(shí)際需要的腳本。

本文從 ECMAScript/JavaScript, DOM, 和頁面載入方面分別介紹幾種簡單的能提高 Web 應(yīng)用程序性能的方法。

目錄

ECMAScript

  1. 避免使用 evalFunction 構(gòu)造函數(shù)
    1. 重寫 eval
    2. 如果你需要函數(shù),那就用函數(shù)
  2. 避免使用 with
  3. 不要在影響性能的關(guān)鍵函數(shù)中使用 try-catch-finally
  4. 分隔 evalwith
  5. 避免使用全局變量
  6. 注意隱式對象轉(zhuǎn)換
  7. 在關(guān)鍵函數(shù)中避免 for-in
  8. 優(yōu)化 string 合并
  9. 基本運(yùn)算符比函數(shù)調(diào)用更快
  10. setTimeout()setInterval()傳送函數(shù)名,而不要傳送字符串

DOM

  1. 重繪和 reflow
    1. 減少 reflow 次數(shù)
    2. 最小化 reflow 影響
  2. 修改 DOM 樹
  3. 修改不可見元素
  4. 測量大小
  5. 一次修改多個樣式值
  6. 用流暢性換取速度
  7. 避免搜索大量節(jié)點(diǎn)
  8. 使用 XPath 提高速度
  9. 避免在遍歷 DOM 時修改 DOM
  10. 使用變量保存 DOM 值

頁面載入

  1. 避免保存來自其他文檔的引用
  2. 快速歷史瀏覽
  3. 使用 XMLHttpRequest
  4. 動態(tài)創(chuàng)建 SCRIPT 元素
  5. location.replace() 控制歷史項(xiàng)

ECMAScript

避免使用 evalFunction 構(gòu)造函數(shù)

每次 eval Function 構(gòu)造函數(shù)作用于字符串表示的源代碼時,腳本引擎都需要將源代碼轉(zhuǎn)換成可執(zhí)行代碼。這是很消耗資源的操作 —— 通常比簡單的函數(shù)調(diào)用慢100倍以上。

eval 函數(shù)效率特別低,由于事先無法知曉傳給 eval 的字符串中的內(nèi)容,eval在其上下文中解釋要處理的代碼,也就是說編譯器無法優(yōu)化上下文,因此只能有瀏覽器在運(yùn)行時解釋代碼。這對性能影響很大。

Function 構(gòu)造函數(shù)比 eval 略好,因?yàn)槭褂么舜a不會影響周圍代碼;但其速度仍很慢。

重寫 eval

eval 不僅效率低下,而且絕大部分情況下完全沒有使用的必要。很多情況下使用 eval 是因?yàn)樾畔⒁宰址问教峁_發(fā)者誤認(rèn)為只有 eval 能使用此信息。下例是一個典型的錯誤:

復(fù)制代碼 代碼如下:
function getProperty(oString) { var oReference; eval('oReference = test.prop.'+oString); return oReference; }

下面的代碼執(zhí)行完全相同的函數(shù),但沒有使用 eval

復(fù)制代碼 代碼如下:
function getProperty(oString) { return test.prop[oString]; }

在 Opera 9, Firefox, 和 Internet Explorer 中后者比前者快95%,在 Safari 中快85%。(注意此比較中不含函數(shù)本身調(diào)用時間。)

如果你需要函數(shù),那就用函數(shù)

下面是常見的 Function 構(gòu)造函數(shù)使用:

復(fù)制代碼 代碼如下:
function addMethod(oObject,oProperty,oFunctionCode) { oObject[oProperty] = new Function(oFunctionCode); } addMethod(myObject,'rotateBy90','this.angle=(this.angle+90)%360'); addMethod(myObject,'rotateBy60','this.angle=(this.angle+60)%360');

下面的代碼沒有使用 Function 構(gòu)造函數(shù),但提供了相同的功能:通過創(chuàng)建匿名函數(shù):

復(fù)制代碼 代碼如下:
function addMethod(oObject,oProperty,oFunction) { oObject[oProperty] = oFunction; } addMethod(myObject,'rotateBy90',function () { this.angle=(this.angle+90)%360; }); addMethod(myObject,'rotateBy60',function () { this.angle=(this.angle+60)%360; });

避免使用 with

盡管看起來挺方便,但 with 效率很低。with 結(jié)構(gòu)又創(chuàng)建了一個作用域,以便使用變量時腳本引擎搜索。這本身只輕微的影響性能。但嚴(yán)重的是編譯時不知道此作用域內(nèi)容,因此編譯器無法像對其他作用域(如函數(shù)產(chǎn)生的作用域)那樣對之優(yōu)化。

另一個高效而且也挺方便的方法是使用變量引用對象,然后使用變量訪問對象屬性。但只有屬性不是 literal type 時才適用,如字符串或布爾值。

考慮下面的代碼:

復(fù)制代碼 代碼如下:
with( test.information.settings.files ) { primary = 'names'; secondary = 'roles'; tertiary = 'references'; }

下面的代碼效率更高:

var testObject = test.information.settings.files; 
testObject.primary = 'names'; 
testObject.secondary = 'roles'; 
testObject.tertiary = 'references'; 

不要在影響性能的關(guān)鍵函數(shù)中使用 try-catch-finally

try-catch-finally 結(jié)構(gòu)比較特殊。和其他語法結(jié)構(gòu)不同,它在 runtime 的當(dāng)前作用域中創(chuàng)建新變量。每當(dāng) catch 執(zhí)行時,就會將捕獲到的 exception 對象賦給一個變量。這個變量不屬于任何腳本。它在 catch 語句開始時被創(chuàng)建,在結(jié)束時被銷毀。

由于此函數(shù)比較特殊,且是在運(yùn)行時動態(tài)創(chuàng)建動態(tài)銷毀,有些瀏覽器對其的處理并不高效。把 catch 語句放在關(guān)鍵循環(huán)中將極大影響性能。

如果可能,應(yīng)在腳本中不頻繁被調(diào)用的地方進(jìn)行異常處理,或通過檢查某種動作是否被支持來避免使用。下面的例子中,如果所需的屬性不存在,將在循環(huán)語句中拋出許多異常:

復(fù)制代碼 代碼如下:
var oProperties = ['first','second','third',...,'nth'], i; for( i = 0; i < oProperties.length; i++ ) { try { test[oProperties[i]].someproperty = somevalue; } catch(e) { ... } }
很多情況下,可把 try-catch-finally 結(jié)構(gòu)移到循環(huán)外部。這樣做稍微改變了程序語義,因?yàn)槿绻麙伋霎惓?,將停止整個循環(huán):
var oProperties = ['first','second','third',...,'nth'], i; 
try { 
for( i = 0; i < oProperties.length; i++ ) { 
test[oProperties[i]].someproperty = somevalue; 
} 
} catch(e) { 
... 
} 

有時可用屬性檢測或其他檢測代替 try-catch-finally 結(jié)構(gòu):

[code]var oProperties = ['first','second','third',...,'nth'], i; for( i = 0; i < oProperties.length; i++ ) { if( test[oProperties[i]] ) { test[oProperties[i]].someproperty = somevalue; } }

分隔 evalwith

因?yàn)?eval 和 with 結(jié)構(gòu)嚴(yán)重影響性能,應(yīng)該盡量避免使用這些結(jié)構(gòu)。但如不得不使用時, 避免在頻繁被調(diào)用的函數(shù)中或循環(huán)中使用這些結(jié)構(gòu)。最好將這些結(jié)構(gòu)放在只運(yùn)行一次,或少量幾次的代碼中,并不要將其放在對性能要求較高的代碼中。

如果可能,盡量將這些結(jié)構(gòu)和其他代碼分隔開,這樣他們就不會影響腳本性能。如將其放在頂級函數(shù)中,或只執(zhí)行一次然后保存運(yùn)行結(jié)果,避免再次使用。

try-catch-finally 結(jié)構(gòu)在一些瀏覽器中也會影響性能,包括 Opera ,因此最好也將其分隔。

避免使用全局變量

全局變量使用簡單,因此很容易禁不住誘惑在腳本中使用全局變量。但有時全局變量也會影響腳本性能。

首先,如果函數(shù)或其他作用域內(nèi)引用了全局變量,則腳本引擎不得不一級一級查看作用域直到搜索到全局作用域。查詢本地作用域變量更快。

其次,全局變量將始終存在在腳本生命周期中。而本地變量在本地作用域結(jié)束后就將被銷毀,其所使用的內(nèi)存也會被垃圾收集器回收。

最后,window 對象也共享全局作用域,也就是說本質(zhì)上是兩個作用域而不是一個。使用全局變量不能像使用本地變量那樣使用前綴,因此腳本引擎要花更多時間查找全局變量。

也可在全局作用域中創(chuàng)建全局函數(shù)。函數(shù)中可以調(diào)用其他函數(shù),隨著函數(shù)調(diào)用級數(shù)增加,腳本引擎需要花更多時間才能找到全局變量以找到全局變量。

考慮下面的簡單例子,is 是全局作用域且函數(shù)使用這兩個全局變量:

復(fù)制代碼 代碼如下:
var i, s = ''; function testfunction() { for( i = 0; i < 20; i++ ) { s += i; } } testfunction();

下面的函數(shù)效率更高。在大多數(shù)瀏覽器中,包括 Opera 9、最新版 Internet Explorer, Firefox, Konqueror 和 Safari,后者執(zhí)行速度比上面代碼快30%。

function testfunction() { 
var i, s = ''; 
for( i = 0; i < 20; i++ ) { 
s += i; 
} 
} 
testfunction(); 

注意隱式對象轉(zhuǎn)換

Literal,如字符串、數(shù)字和布爾值在 ECMAScript 中有兩種表示方法。 每個類型都可以創(chuàng)建變量值或?qū)ο?。?var oString = 'some content';, 創(chuàng)建了字符串值,而 var oString = new String('some content');創(chuàng)建了字符串對象。

所有的屬性和方法都定義在 string 對象中,而不是 string 值中。每次使用 string 值的方法或?qū)傩?,ECMAScript 引擎都會隱式的用相同 string 值創(chuàng)建新的 string 對象。此對象只用于此請求,以后每次視圖調(diào)用 string值方法是都會重新創(chuàng)建。

下面的代碼將要求腳本引擎創(chuàng)建21個新 string 對象,每次使用 length 屬性時都會產(chǎn)生一個,每一個 charAt 方法也會產(chǎn)生一個:

var s = '0123456789'; 
for( var i = 0; i < s.length; i++ ) { 
s.charAt(i); 
} 

下面的代碼和上面相同,但只創(chuàng)建了一個對象,因此其效率更高:

復(fù)制代碼 代碼如下:
var s = new String('0123456789'); for( var i = 0; i < s.length; i++ ) { s.charAt(i); }

如果代碼中常調(diào)用 literal 值的方法,你應(yīng)像上面例子那樣考慮創(chuàng)建對象。

注意本文中大部分技巧對于所有瀏覽器都有效,但此技巧特別針對于 Opera。此優(yōu)化技巧在 Internet Explorer 和 Firefox 中改進(jìn)效果沒有在 Opera 中明顯。

在關(guān)鍵函數(shù)中避免 for-in

for-in 常被誤用,特別是簡單的 for 循環(huán)更合適時。for-in 循環(huán)需要腳本引擎創(chuàng)建所有可枚舉的屬性列表,然后檢查是否存在重復(fù)。

有時腳本已知可枚舉的屬性。這時簡單的 for 循環(huán)即可遍歷所有屬性,特別是當(dāng)使用順序數(shù)字枚舉時,如數(shù)組中。

下面是不正確的 for-in 循環(huán)使用:

復(fù)制代碼 代碼如下:
var oSum = 0; for( var i in oArray ) { oSum += oArray[i]; }

for 循環(huán)無疑會更高效:

復(fù)制代碼 代碼如下:
var oSum = 0; var oLength = oArray.length; for( var i = 0; i < oLength; i++ ) { oSum += oArray[i]; }

優(yōu)化 string 合并

字符串合并是比較慢的。+ 運(yùn)算符并不管是否將結(jié)果保存在變量中。它會創(chuàng)建新 string 對象,并將結(jié)果賦給此對象;也許新對象會被賦給某個變量。下面是一個常見的字符串合并語句:

復(fù)制代碼 代碼如下:
a += 'x' + 'y';

此代碼首先創(chuàng)建臨時string對象保存合并后的'xy'值,然后和a變量合并,最后將結(jié)果賦給a。下面的代碼使用兩條分開的命令,但每次都直接賦值給a ,因此不需要創(chuàng)建臨時string對象。結(jié)果在大部分瀏覽器中,后者比前者快20%,而且消耗更少的內(nèi)存:

復(fù)制代碼 代碼如下:
a += 'x'; a += 'y';

基本運(yùn)算符比函數(shù)調(diào)用更快

盡管單獨(dú)使用效果不明顯,但如果在需要高性能的關(guān)鍵循環(huán)和函數(shù)中使用基本運(yùn)算符代替函數(shù)調(diào)用將可能提高腳本性能。例子包括數(shù)組的 push 方法,其效率低于直接在數(shù)組末位賦值。另一個例子是 Math 對象方法,大部分情況下,簡單的數(shù)學(xué)運(yùn)算符效率更高更合適。

復(fù)制代碼 代碼如下:
var min = Math.min(a,b); A.push(v);

下面代碼實(shí)現(xiàn)相同功能,但效率更高:

復(fù)制代碼 代碼如下:
var min = a < b ? a : b; A[A.length] = v;

setTimeout()setInterval()傳送函數(shù)名,而不要傳送字符串

setTimeout()setInterval() 方法近似于 eval。如果傳進(jìn)參數(shù)是字符串,則在一段時間之后,會和 eval一樣執(zhí)行字符串值,當(dāng)然其低效率也和 eval 一樣。

但這些方法也可以接受函數(shù)作為第一個參數(shù)。在一段時間后將調(diào)用此函數(shù),但此函數(shù)可在編譯時被解釋和優(yōu)化,也就是說會有更好的性能。典型的使用 string 作為參數(shù)例子如下:

復(fù)制代碼 代碼如下:
setInterval('updateResults()',1000); setTimeout('x+=3;prepareResult();if(!hasCancelled){runmore();}',500);

第一個語句可以直接傳遞函數(shù)名。第二個語句中,可以使用匿名函數(shù)封裝代碼:

setInterval(updateResults,1000); 
setTimeout(function () { 
x += 3; 
prepareResult(); 
if( !hasCancelled ) { 
runmore(); 
} 
},500); 

需要注意的是 timeout或時間延遲可能并不準(zhǔn)確。通常瀏覽器會花比要求更多的時間。有些瀏覽器會稍微提早完成下一個延遲以補(bǔ)償。有些瀏覽器每次可能都會等待準(zhǔn)確時間。很多因素,如 CPU 速度、線程狀態(tài)和 JavaScript負(fù)載都會影響時間延遲的精度。大多數(shù)瀏覽器無法提供1ms以下的延遲,可能會設(shè)置最小可能延遲,通常在10 和 100 ms之間。

DOM

通常主要有三種情況引起 DOM 運(yùn)行速度變慢。第一就是執(zhí)行大量 DOM 操作的腳本,如從獲取的數(shù)據(jù)中建造新的 DOM 樹。第二種情況是腳本引起太多的 reflow 或重繪。第三種情況是使用較慢的 DOM 節(jié)點(diǎn)定位方法。

第二種和第三種情況比較常見且對性能影響比較嚴(yán)重,因此先介紹前兩種情況。

重繪(Repaint)和 reflow

重繪也被稱為重畫,每當(dāng)以前不可見的元素變得可見(或反之)時就需要重繪操作;重繪不會改變頁面布局。如給元素添加輪廓、改變背景顏色、改變樣式。重繪對性能影響很大,因?yàn)樾枰_本引擎搜索所有元素以確定哪些是可見的及哪些是應(yīng)被顯示的。

Reflow 是更大規(guī)模的變化。當(dāng) DOM 數(shù)被改變時、影響布局的樣式被修改時、當(dāng)元素的 className屬性被修改時或當(dāng)瀏覽器窗口大小變化時都會引起 reflow。腳本引擎必須 reflow 相關(guān)元素以確定哪些部分不應(yīng)被現(xiàn)實(shí)。其子節(jié)點(diǎn)也會被reflow 以考慮其父節(jié)點(diǎn)的新布局。DOM 中此元素之后出現(xiàn)的元素也被 reflow以計(jì)算新布局,因?yàn)樗鼈兊奈恢每赡芤驯灰苿恿?。祖先?jié)點(diǎn)也需要 reflow 以適應(yīng)子節(jié)點(diǎn)大小的改變??傊性囟夹璞恢乩L。

Reflow 從性能角度來說是非常耗時的操作,是導(dǎo)致 DOM 腳本較慢的主要原因之一,特別在手機(jī)等處理能力較弱的設(shè)備上。很多情況下,reflow 和重新布局整個網(wǎng)頁耗時相近。

減少 reflow 次數(shù)

很多情況下腳本需要進(jìn)行會引起 reflow 或重繪的操作,如動畫就需要 reflow 操作,因此 reflow 是 Web 開發(fā)不可或缺的特性。為了讓腳本能快速運(yùn)行,應(yīng)在不影響整體視覺效果的情況下盡量減少 reflow 次數(shù)。

瀏覽器可以選擇緩存 reflow 操作,如可以等到腳本線程結(jié)束后才 reflow 以呈現(xiàn)變化。Opera 可以等待足夠數(shù)量的改變后才reflow、或等待足夠長時間后才 reflow、或等待腳本線程結(jié)束后才reflow。也就是說如果一個腳本線程中的發(fā)生很多間隔很小的改變時,可能只引起一個 reflow 。但開發(fā)者不能依賴此特性,特別是考慮到運(yùn)行Opera 的不同設(shè)備的運(yùn)算速度有很大差異。

注意不同元素的 reflow 消耗時間不同。Reflow 表格元素消耗的時間最多是 Reflow 塊元素時間的3倍。

最小化 reflow 影響

正常的 reflow 可能影響整個頁面。reflow 的頁面內(nèi)容越多,則 reflow 操作的時間也越長。Reflow的頁面內(nèi)容越多,需要的時間也就越長。位置固定的元素不影響頁面的布局,因此如果它們 reflow 則只需 reflow其本身。其背后的網(wǎng)頁需要被重繪,但這比 reflow 整個頁面要快得多。

所以動畫不應(yīng)該被用于整個頁面,最好用于固定位置元素。大部分動畫符合此要求。

修改 DOM 樹

修改 DOM 樹導(dǎo)致 reflow 。向 DOM 中添加新元素、修改 text 節(jié)點(diǎn)值或修改屬性都可能導(dǎo)致 reflow。順序執(zhí)行多個修改會引起超過一個 reflow,因此最好將多個修改放在不可見的 DOM 樹 fragment 中。這樣就只需要一次 DOM 修改操作:

復(fù)制代碼 代碼如下:
var docFragm = document.createDocumentFragment(); var elem, contents; for( var i = 0; i < textlist.length; i++ ) { elem = document.createElement('p'); contents = document.createTextNode(textlist[i]); elem.appendChild(contents); docFragm.appendChild(elem); } document.body.appendChild(docFragm);

也可以在元素的克隆版本中進(jìn)行多個 DOM 樹修改操作,在修改結(jié)束后用克隆版本替換原版本即可,這樣只需要一個 reflow操作。注意如果元素中包含表單控件,則不能使用此技巧,因?yàn)橛脩羲鲂薷膶o法反映在 DOM樹種。此技巧也不應(yīng)該用于綁定事件處理器的元素,因?yàn)槔碚撋喜粦?yīng)該克隆這些元素。

復(fù)制代碼 代碼如下:
var original = document.getElementById('container'); var cloned = original.cloneNode(true); cloned.setAttribute('width','50%'); var elem, contents; for( var i = 0; i < textlist.length; i++ ) { elem = document.createElement('p'); contents = document.createTextNode(textlist[i]); elem.appendChild(contents); cloned.appendChild(elem); } original.parentNode.replaceChild(cloned,original);

修改不可見元素

如果一個元素的 display 樣式被設(shè)置為 none,即使其內(nèi)容變化也不再需要重繪此元素,因?yàn)楦揪筒粫@示此元素??梢岳眠@一點(diǎn)。如果需要對一個元素或其內(nèi)容做出多個修改,又無法將這些更改放在一個重繪中,則可以先將元素設(shè)置為 display:none ,做出修改后,在把元素改回原來狀態(tài)。

上面方法將導(dǎo)致兩個額外的 reflow,一個是隱藏元素時另一個是重新顯示此元素時,但此方法的總體效率仍較高。如果隱藏的元素影響滾動條位置,上面的方法也有可能會引起滾動條跳動。但此技術(shù)也被用于固定位置元素而不會引起任何不好看的影響。

復(fù)制代碼 代碼如下:
var posElem = document.getElementById('animation'); posElem.style.display = 'none'; posElem.appendChild(newNodes); posElem.style.width = '10em'; ... other changes ... posElem.style.display = 'block';

測量大小

如上面所述,瀏覽器可能會緩存多個修改一起執(zhí)行,并只執(zhí)行一次 reflow 。但注意為保證結(jié)果正確,測量元素大小也會引起 reflow 。盡管這不會造成任何重繪,但仍會在后臺進(jìn)行 reflow 操作。

使用 offsetWidth 這樣的屬性或 getComputedStyle 這樣的方法都會引起 reflow 。即使不使用返回的結(jié)果,上述操作也會引起立即 reflow。如果重復(fù)需要測量結(jié)果,可以考慮只測量一次但用變量保存結(jié)果。

復(fù)制代碼 代碼如下:
var posElem = document.getElementById('animation'); var calcWidth = posElem.offsetWidth; posElem.style.fontSize = ( calcWidth / 10 ) + 'px'; posElem.firstChild.style.marginLeft = ( calcWidth / 20 ) + 'px'; posElem.style.left = ( ( -1 * calcWidth ) / 2 ) + 'px'; ... other changes ...

一次修改多個樣式值

與 DOM 樹修改相似,可將多個樣式修改一次進(jìn)行,以盡量減少重繪或 reflow數(shù)目。常見設(shè)置樣式方法是逐個設(shè)置:

復(fù)制代碼 代碼如下:
var toChange = document.getElementById('mainelement'); toChange.style.background = '#333'; toChange.style.color = '#fff'; toChange.style.border = '1px solid #00f';

上面代碼可能引起多次 reflow 和重繪。有兩種改進(jìn)方法。如果元素采用了多個樣式,而且這些樣式值事先知道,可以通過修改元素 class 使用新樣式:

div { 
background: #ddd; 
color: #000; 
border: 1px solid #000; 
} 
div.highlight { 
background: #333; 
color: #fff; 
border: 1px solid #00f; 
} 
... 
document.getElementById('mainelement').className = 'highlight'; 

第二種方法是為元素定義新樣式,而不是一個個賦值。這主要用于動態(tài)修改,如在動畫中,無法事前知道新樣式值。通過使用 style 對象的 cssText 屬性,或者通過 setAttribute. 可以實(shí)現(xiàn)此技巧。Internet Explorer 不允許第二種形式,支持第一種形式。有些較老的瀏覽器,包括 Opera 8 需要使用第二種形式,不支持第一種形式。最簡單的方式是測試看是否支持第一種形式,如果支持就使用,如果不支持則使用第二種形式。

復(fù)制代碼 代碼如下:
var posElem = document.getElementById('animation'); var newStyle = 'background: ' + newBack + ';' + 'color: ' + newColor + ';' + 'border: ' + newBorder + ';'; if( typeof( posElem.style.cssText ) != 'undefined' ) { posElem.style.cssText = newStyle; } else { posElem.setAttribute('style',newStyle); }

用流暢性換取速度

作為開發(fā)者,當(dāng)然想要動畫運(yùn)行的越流暢越好,通常使用較小的時間間隔或較小的變化。如每10ms更新一次動畫,或者每次移動1個像素。此動畫可能在桌面電腦上或某些瀏覽器中可以完美運(yùn)行。但10ms時間間隔可能是瀏覽器使用100%CPU才能達(dá)到的最小值。有些瀏覽器甚至不能完成——要求每秒100個 reflow 對大部分瀏覽器來說都不容易。低性能的電腦或者其他設(shè)備可能無法達(dá)到此種速度,在這些設(shè)備上動畫可能非常慢甚至失去響應(yīng)。

因此最好暫時把開發(fā)者的驕傲放在一邊,犧牲流暢性而換取速度。把時間間隔改為50ms或把動畫步長設(shè)為5個像素,會消耗更少的計(jì)算資源,在低性能設(shè)備上也能正常運(yùn)行。

避免搜索大量節(jié)點(diǎn)

當(dāng)需要查找節(jié)點(diǎn)時,盡量使用 DOM 內(nèi)置方法和集合縮小搜索范圍。如你想要定位某個包含某種屬性的元素,可使用下面代碼:

復(fù)制代碼 代碼如下:
var allElements = document.getElementsByTagName('*'); for( var i = 0; i < allElements.length; i++ ) { if( allElements[i].hasAttribute('someattr') ) { ... } }

即使沒聽說過 XPath 這樣的高級技巧,也可以看出上面的代碼有兩個問題導(dǎo)致速度變慢。首先它搜索每一個元素,而不是嘗試縮小搜索范圍。其次即使已經(jīng)找到所需元素上賣弄代碼還繼續(xù)搜索。如果已知要找的元素在 id 為 inhere的 div 中,最好使用下面的代碼:

復(fù)制代碼 代碼如下:
var allElements = document.getElementById('inhere').getElementsByTagName('*'); for( var i = 0; i < allElements.length; i++ ) { if( allElements[i].hasAttribute('someattr') ) { ... break; } }

如果已知要找元素是 div 的直接子節(jié)點(diǎn),則下面的代碼速度更快:

復(fù)制代碼 代碼如下:
var allChildren = document.getElementById('inhere').childNodes; for( var i = 0; i < allChildren.length; i++ ) { if( allChildren[i].nodeType == 1 && allChildren[i].hasAttribute('someattr') ) { ... break; } }

基本的思想就是盡量避免逐個查看 DOM 節(jié)點(diǎn)。DOM 有很多更好更快的方法,如 DOM 2 Traversal TreeWalker,效率要高于遞歸查找 childNodes 集合。

使用 XPath 提高速度

假如需要基于 H2-H4 元素在 HTML 網(wǎng)頁中創(chuàng)建目錄。在 HTML 中標(biāo)題元素可以出現(xiàn)在很多地方,因此無法使用遞歸函數(shù)獲取這些元素。傳統(tǒng) DOM 可能使用如下方法:

復(fù)制代碼 代碼如下:
var allElements = document.getElementsByTagName('*'); for( var i = 0; i < allElements.length; i++ ) { if( allElements[i].tagName.match(/^h[2-4]$/i) ) { ... } }

若網(wǎng)頁有超過2000個元素,此方法速度會很慢。如果支持 XPath,則可以使用一個快得多的方法,因?yàn)?XPath 查詢引擎可比需被解釋的JavaScript 更好的被優(yōu)化。在有些情況下,XPath 速度可能會快2個數(shù)量級以上。下面代碼和上面完成一樣的功能,但使用 XPath因此速度要更快:

復(fù)制代碼 代碼如下:
var headings = document.evaluate( '//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null ); var oneheading; while( oneheading = headings.iterateNext() ) { ... }

下面版本代碼融合上述兩種方法;在支持 XPath 的地方使用快速方法,在不支持時使用傳統(tǒng) DOM 方法:

if( document.evaluate ) { 
var headings = document.evaluate( '//h2|//h3|//h4', document, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null ); 
var oneheading; 
while( oneheading = headings.iterateNext() ) { 
... 
} 
} else { 
var allElements = document.getElementsByTagName('*'); 
for( var i = 0; i < allElements.length; i++ ) { 
if( allElements[i].tagName.match(/^h[2-4]$/i) ) { 
... 
} 
} 
} 

避免在遍歷 DOM 時修改 DOM

有些 DOM 集合是實(shí)時的,如果在你的腳本遍歷列表時相關(guān)元素產(chǎn)生變化,則此集合會立刻變化而不需要等待腳本遍歷結(jié)束。childNodes 集合和 getElementsByTagName 返回的節(jié)點(diǎn)列表都是這樣的實(shí)時集合。

如果在遍歷這樣的集合的同時向其中添加元素,則可能會遇到無限循環(huán),因?yàn)槟悴煌5南蛄斜碇刑砑釉兀肋h(yuǎn)也不會碰到列表結(jié)束。這不是唯一的問題。為提高性能,可能會對這些集合做出優(yōu)化,如記住其長度、記住腳本中上一個訪問元素序號,這樣在你訪問下一個元素時可快速定位。

如果你此時修改 DOM 樹,即使修改的元素不在此集合中,集合還是會重新搜索以查看是否有新元素。這樣就無法記住上一個訪問元素序號或記住集合長度,因?yàn)榧媳旧砜赡芤呀?jīng)變了,這樣就無法使用優(yōu)化:

var allPara = document.getElementsByTagName('p'); 
for( var i = 0; i < allPara.length; i++ ) { 
allPara[i].appendChild(document.createTextNode(i)); 
} 

下面的代碼在 Opera 和 Internet Explorer 等主流瀏覽器中比上面代碼快10倍以上。先創(chuàng)建一個要修改元素的靜態(tài)列表,然后遍歷靜態(tài)列表并作出相應(yīng)修改,而不是遍歷 getElementsByTagName 返回的節(jié)點(diǎn)列表:

var allPara = document.getElementsByTagName('p'); 
var collectTemp = []; 
for( var i = 0; i < allPara.length; i++ ) { 
collectTemp[collectTemp.length] = allPara[i]; 
} 
for( i = 0; i < collectTemp.length; i++ ) { 
collectTemp[i].appendChild(document.createTextNode(i)); 
} 
collectTemp = null; 

使用變量保存 DOM 值

有些 DOM 返回值無法緩存,每次調(diào)用時都會重新調(diào)用函數(shù)。如 getElementById 方法。下面是一個低效率代碼的例子:

復(fù)制代碼 代碼如下:
document.getElementById('test').property1 = 'value1'; document.getElementById('test').property2 = 'value2'; document.getElementById('test').property3 = 'value3'; document.getElementById('test').property4 = 'value4';

此代碼為定位同一個對象調(diào)用了四次 getElementById 方法。下面的代碼只調(diào)用了一次并將結(jié)果保存在變量中,單看這一個操作可能比上面單個操作要略慢,因?yàn)樾枰獔?zhí)行賦值語句。但后面不再需要調(diào)用 getElementById 方法!下面的代碼比上面的代碼要快5-10倍:

復(fù)制代碼 代碼如下:
var sample = document.getElementById('test'); sample.property1 = 'value1'; sample.property2 = 'value2'; sample.property3 = 'value3'; sample.property4 = 'value4';

 

頁面載入

避免保存來自其他文檔的引用

如果文檔訪問過其他文檔中的節(jié)點(diǎn)或?qū)ο螅谀_本結(jié)束后避免保留這些引用。如果在全局變量或?qū)ο髮傩灾斜4孢^這些引用,通過設(shè)置為 null 清除之或者直接刪除之。

原因是另一個文檔被銷毀后,如彈出窗口被關(guān)閉,盡管那個文檔已經(jīng)不再了,所有對那個文檔中對象的引用都會在內(nèi)存中保存整個 DOM 樹和腳本環(huán)境。這也適用那些包含在frame,內(nèi)聯(lián) frame,或 OBJECT 元素中的網(wǎng)頁。.

復(fù)制代碼 代碼如下:
var remoteDoc = parent.frames['sideframe'].document; var remoteContainer = remoteDoc.getElementById('content'); var newPara = remoteDoc.createElement('p'); newPara.appendChild(remoteDoc.createTextNode('new content')); remoteContainer.appendChild(newPara); //remove references remoteDoc = null; remoteContainer = null; newPara = null;

快速歷史瀏覽(history navigation)

Opera (和許多其他瀏覽器)默認(rèn)使用快速歷史瀏覽。當(dāng)用戶點(diǎn)擊后退或前進(jìn)時,將記錄當(dāng)前頁面的狀態(tài)及頁面中的腳本。當(dāng)用戶回到剛才的頁面時,將立即顯示剛才的頁面,如同從沒有離開此頁一樣。不需要重新載入頁面也不需要重新初始化。腳本繼續(xù)運(yùn)行,DOM也和離開此頁前完全相同。對用戶來說這樣反應(yīng)很快,載入較慢的網(wǎng)頁應(yīng)用程序會有更好的性能。

盡管 Opera 提供開發(fā)者控制此行為的方式,最好還是盡量保持快速歷史瀏覽功能。也就是說最好避免會影響此功能的動作,包括提交表單時禁用表單控件或讓頁面內(nèi)容透明或不可見的漸出特效。

簡單的解決方法是使用 onunload 監(jiān)聽器 reset 漸出效果或重新 enable 表單控件。注意對有些瀏覽器來說,如 Firefox 和 Safari,為 unload 事件添加監(jiān)聽器會禁用歷史瀏覽。而在 Opera 中禁用提交按鈕會導(dǎo)致禁用歷史瀏覽。

復(fù)制代碼 代碼如下:
window.onunload = function () { document.body.style.opacity = '1'; };

使用 XMLHttpRequest

此技巧不一定適用于每一個項(xiàng)目,但它能顯著降低從服務(wù)器下載數(shù)據(jù)量,也能避免重載頁面時銷毀及創(chuàng)建腳本環(huán)境的開銷。開始時正常載入頁面,然后使用 XMLHttpRequest 下載最少量的新內(nèi)容。這樣 JavaScript 環(huán)境會一直存在。

注意此方法也可能會導(dǎo)致問題。首先此方法完全破壞歷史瀏覽。盡管可通過內(nèi)聯(lián)frame儲存信息來解決此問題,但這顯然不符合使用XMLHttpRequest 的初衷。因此盡量少用,只在不需要回退到先前內(nèi)容時使用。此方法還會影響輔助器具的使用( assistivedevice),因?yàn)閷o法察覺 DOM 已被更改,因此最好在不會引起問題的地方使用XMLHttpRequest。

若 JavaScript 不可用或不支持 XMLHttpRequest則此技巧也會失效。最簡單避免此問題的方法是使用正常鏈接指向新頁面。增加一個檢測鏈接是否被激活的事件處理器。處理器可以探測是否支持XMLHttpRequest ,如果支持則載入新數(shù)據(jù)并阻止鏈接默認(rèn)動作。載入新數(shù)據(jù)后,用其取代頁面的部分內(nèi)容,然后 request對象就可以被銷毀并允許垃圾收集器回收內(nèi)存資源。

復(fù)制代碼 代碼如下:
document.getElementById('nextlink').onclick = function () { if( !window.XMLHttpRequest ) { return true; } var request = new XMLHttpRequest(); request.onreadystatechange = function () { if( request.readyState != 4 ) { return; } var useResponse = request.responseText.replace( /^[\w\W]*<div id="container">|<\/div>\s*<\/body>[\w\W]*$/g , '' ); document.getElementById('container').innerHTML = useResponse; request.onreadystatechange = null; request = null; }; request.open( 'GET', this.href, true ); request.send(null); return false; }

動態(tài)創(chuàng)建 SCRIPT 元素

加載和處理腳本需要時間,但有些腳本在載入后卻從來未被使用。載入這樣的腳本浪費(fèi)時間和資源,并影響當(dāng)前的腳本執(zhí)行,因此最好不要引用這種不用的腳本。可以通過簡單的加載腳本判斷需要哪些腳本,并只為后面需要的腳本創(chuàng)建 script 元素。

理論上,這個加載腳本可在頁面載入結(jié)束后通過創(chuàng)建 SCRIPT 元素加入 DOM。這在所有主流瀏覽器中都可以正常工作,但這可能對瀏覽器的提出更多的要求,甚至大于要載入的腳本本身。而且在頁面載入之前可能就需要腳本,因此最好在頁面加載過程中,通過 document.write 創(chuàng)建 script 標(biāo)簽。記住一定要轉(zhuǎn)義‘/'字符防止終止當(dāng)前腳本運(yùn)行:

復(fù)制代碼 代碼如下:
if( document.createElement && document.childNodes ) { document.write('<script type="text\/javascript" src="dom.js"><\/script>'); } if( window.XMLHttpRequest ) { document.write('<script type="text\/javascript" src="xhr.js"><\/script>'); }

location.replace() 控制歷史項(xiàng)

有時需要通過腳本修改頁面地址。常見的方法是給 location.href 賦新地址。這將和打開新鏈接一樣添加新歷史項(xiàng)、載入新頁面。

有時不想添加新歷史項(xiàng),因?yàn)橛脩舨恍枰氐角懊娴捻撁?。這在內(nèi)存資源有限的設(shè)備中很有用。通過替換歷史項(xiàng)恢復(fù)當(dāng)前頁面所使用的內(nèi)存??梢酝ㄟ^ location.replace()方法實(shí)現(xiàn)。

復(fù)制代碼 代碼如下:
location.replace('newpage.html');

注意頁面仍被保存在 cache 中,仍占用內(nèi)存,但比保存在歷史中要少的多。

相關(guān)文章

最新評論