JavaScript 高效運(yùn)行代碼分析
作者 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
- 避免使用
eval
或Function
構(gòu)造函數(shù) - 避免使用
with
- 不要在影響性能的關(guān)鍵函數(shù)中使用
try-catch-finally
- 分隔
eval
和with
- 避免使用全局變量
- 注意隱式對象轉(zhuǎn)換
- 在關(guān)鍵函數(shù)中避免
for-in
- 優(yōu)化 string 合并
- 基本運(yùn)算符比函數(shù)調(diào)用更快
- 向
setTimeout()
和setInterval()
傳送函數(shù)名,而不要傳送字符串
DOM
- 重繪和 reflow
- 修改 DOM 樹
- 修改不可見元素
- 測量大小
- 一次修改多個樣式值
- 用流暢性換取速度
- 避免搜索大量節(jié)點(diǎn)
- 使用 XPath 提高速度
- 避免在遍歷 DOM 時修改 DOM
- 使用變量保存 DOM 值
頁面載入
ECMAScript
避免使用 eval
或 Function
構(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 能使用此信息。下例是一個典型的錯誤:
下面的代碼執(zhí)行完全相同的函數(shù),但沒有使用 eval
:
在 Opera 9, Firefox, 和 Internet Explorer 中后者比前者快95%,在 Safari 中快85%。(注意此比較中不含函數(shù)本身調(diào)用時間。)
如果你需要函數(shù),那就用函數(shù)
下面是常見的 Function
構(gòu)造函數(shù)使用:
下面的代碼沒有使用 Function
構(gòu)造函數(shù),但提供了相同的功能:通過創(chuàng)建匿名函數(shù):
避免使用 with
盡管看起來挺方便,但 with
效率很低。with
結(jié)構(gòu)又創(chuàng)建了一個作用域,以便使用變量時腳本引擎搜索。這本身只輕微的影響性能。但嚴(yán)重的是編譯時不知道此作用域內(nèi)容,因此編譯器無法像對其他作用域(如函數(shù)產(chǎn)生的作用域)那樣對之優(yōu)化。
另一個高效而且也挺方便的方法是使用變量引用對象,然后使用變量訪問對象屬性。但只有屬性不是 literal type 時才適用,如字符串或布爾值。
考慮下面的代碼:
下面的代碼效率更高:
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)語句中拋出許多異常:
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):
分隔 eval
和 with
因?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ù)增加,腳本引擎需要花更多時間才能找到全局變量以找到全局變量。
考慮下面的簡單例子,i 和 s 是全局作用域且函數(shù)使用這兩個全局變量:
下面的函數(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)建了一個對象,因此其效率更高:
如果代碼中常調(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)使用:
for
循環(huán)無疑會更高效:
優(yōu)化 string 合并
字符串合并是比較慢的。+
運(yùn)算符并不管是否將結(jié)果保存在變量中。它會創(chuàng)建新 string 對象,并將結(jié)果賦給此對象;也許新對象會被賦給某個變量。下面是一個常見的字符串合并語句:
此代碼首先創(chuàng)建臨時string對象保存合并后的'xy'值,然后和a變量合并,最后將結(jié)果賦給a。下面的代碼使用兩條分開的命令,但每次都直接賦值給a ,因此不需要創(chuàng)建臨時string對象。結(jié)果在大部分瀏覽器中,后者比前者快20%,而且消耗更少的內(nèi)存:
基本運(yùn)算符比函數(shù)調(diào)用更快
盡管單獨(dú)使用效果不明顯,但如果在需要高性能的關(guān)鍵循環(huán)和函數(shù)中使用基本運(yùn)算符代替函數(shù)調(diào)用將可能提高腳本性能。例子包括數(shù)組的 push 方法,其效率低于直接在數(shù)組末位賦值。另一個例子是 Math 對象方法,大部分情況下,簡單的數(shù)學(xué)運(yùn)算符效率更高更合適。
下面代碼實(shí)現(xiàn)相同功能,但效率更高:
向 setTimeout()
和 setInterval()
傳送函數(shù)名,而不要傳送字符串
setTimeout()
和 setInterval()
方法近似于 eval
。如果傳進(jìn)參數(shù)是字符串,則在一段時間之后,會和 eval
一樣執(zhí)行字符串值,當(dāng)然其低效率也和 eval
一樣。
但這些方法也可以接受函數(shù)作為第一個參數(shù)。在一段時間后將調(diào)用此函數(shù),但此函數(shù)可在編譯時被解釋和優(yōu)化,也就是說會有更好的性能。典型的使用 string 作為參數(shù)例子如下:
第一個語句可以直接傳遞函數(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 修改操作:
也可以在元素的克隆版本中進(jìn)行多個 DOM 樹修改操作,在修改結(jié)束后用克隆版本替換原版本即可,這樣只需要一個 reflow操作。注意如果元素中包含表單控件,則不能使用此技巧,因?yàn)橛脩羲鲂薷膶o法反映在 DOM樹種。此技巧也不應(yīng)該用于綁定事件處理器的元素,因?yàn)槔碚撋喜粦?yīng)該克隆這些元素。
修改不可見元素
如果一個元素的 display 樣式被設(shè)置為 none,即使其內(nèi)容變化也不再需要重繪此元素,因?yàn)楦揪筒粫@示此元素??梢岳眠@一點(diǎn)。如果需要對一個元素或其內(nèi)容做出多個修改,又無法將這些更改放在一個重繪中,則可以先將元素設(shè)置為 display
:none ,做出修改后,在把元素改回原來狀態(tài)。
上面方法將導(dǎo)致兩個額外的 reflow,一個是隱藏元素時另一個是重新顯示此元素時,但此方法的總體效率仍較高。如果隱藏的元素影響滾動條位置,上面的方法也有可能會引起滾動條跳動。但此技術(shù)也被用于固定位置元素而不會引起任何不好看的影響。
測量大小
如上面所述,瀏覽器可能會緩存多個修改一起執(zhí)行,并只執(zhí)行一次 reflow 。但注意為保證結(jié)果正確,測量元素大小也會引起 reflow 。盡管這不會造成任何重繪,但仍會在后臺進(jìn)行 reflow 操作。
使用 offsetWidth 這樣的屬性或 getComputedStyle 這樣的方法都會引起 reflow 。即使不使用返回的結(jié)果,上述操作也會引起立即 reflow。如果重復(fù)需要測量結(jié)果,可以考慮只測量一次但用變量保存結(jié)果。
一次修改多個樣式值
與 DOM 樹修改相似,可將多個樣式修改一次進(jìn)行,以盡量減少重繪或 reflow數(shù)目。常見設(shè)置樣式方法是逐個設(shè)置:
上面代碼可能引起多次 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ā)者,當(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)置方法和集合縮小搜索范圍。如你想要定位某個包含某種屬性的元素,可使用下面代碼:
即使沒聽說過 XPath 這樣的高級技巧,也可以看出上面的代碼有兩個問題導(dǎo)致速度變慢。首先它搜索每一個元素,而不是嘗試縮小搜索范圍。其次即使已經(jīng)找到所需元素上賣弄代碼還繼續(xù)搜索。如果已知要找的元素在 id 為 inhere的 div 中,最好使用下面的代碼:
如果已知要找元素是 div 的直接子節(jié)點(diǎn),則下面的代碼速度更快:
基本的思想就是盡量避免逐個查看 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 可能使用如下方法:
若網(wǎng)頁有超過2000個元素,此方法速度會很慢。如果支持 XPath,則可以使用一個快得多的方法,因?yàn)?XPath 查詢引擎可比需被解釋的JavaScript 更好的被優(yōu)化。在有些情況下,XPath 速度可能會快2個數(shù)量級以上。下面代碼和上面完成一樣的功能,但使用 XPath因此速度要更快:
下面版本代碼融合上述兩種方法;在支持 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 方法。下面是一個低效率代碼的例子:
此代碼為定位同一個對象調(diào)用了四次 getElementById 方法。下面的代碼只調(diào)用了一次并將結(jié)果保存在變量中,單看這一個操作可能比上面單個操作要略慢,因?yàn)樾枰獔?zhí)行賦值語句。但后面不再需要調(diào)用 getElementById 方法!下面的代碼比上面的代碼要快5-10倍:
頁面載入
避免保存來自其他文檔的引用
如果文檔訪問過其他文檔中的節(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)頁。.
快速歷史瀏覽(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)致禁用歷史瀏覽。
使用 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)存資源。
動態(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)行:
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)。
注意頁面仍被保存在 cache 中,仍占用內(nèi)存,但比保存在歷史中要少的多。
相關(guān)文章
純javascript實(shí)現(xiàn)選擇框的全選與反選功能
這篇文章主要介紹了純javascript實(shí)現(xiàn)選擇框的全選與反選 ,需要的朋友可以參考下2019-04-04javascript中this做事件參數(shù)相關(guān)問題解答
有關(guān)this想必大家早有所耳聞,只不過在使用中不是那么在意而已,接下來為大家介紹下this做事件參數(shù)問題,感興趣的你可不要錯過了哈2013-03-03javascript html5 canvas實(shí)現(xiàn)可拖動省份的中國地圖
這篇文章主要介紹了javascript html5 canvas實(shí)現(xiàn)可拖動省份的中國地圖的相關(guān)資料,需要的朋友可以參考下2016-03-03前端JavaScript實(shí)現(xiàn)大數(shù)據(jù)前后模糊搜索的方法詳解
這篇文章主要為大家詳細(xì)介紹了前端JavaScript實(shí)現(xiàn)大數(shù)據(jù)前后模糊搜索的四個常見方法,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2023-12-12javascript網(wǎng)頁關(guān)閉時提醒效果腳本
當(dāng)頁面載入和關(guān)閉時會出現(xiàn)一些提示信息的代碼。方便提醒用戶,但不建議多用,讓人感到反感。2008-10-10JavaScript判斷字符長度、數(shù)字、Email、電話等常用判斷函數(shù)分享
這篇文章主要介紹了JavaScript判斷字符長度、數(shù)字、Email、電話等常用判斷函數(shù)分享,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-04-04JavaScript中7種位運(yùn)算符在實(shí)戰(zhàn)的妙用
位運(yùn)算是在數(shù)字底層(即表示數(shù)字的 32 個數(shù)位)進(jìn)行運(yùn)算的,下面這篇文章主要給大家介紹了關(guān)于JavaScript中7種位運(yùn)算符在實(shí)戰(zhàn)的妙用,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-06-06