關(guān)于前端性能優(yōu)化問題詳解
出處:http://segmentfault.com/blogs
前端性能優(yōu)化指南
AJAX優(yōu)化
- 緩存
AJAX
:* `異步`并不等于 `即時(shí)`。
- 請求使用
GET
:* 當(dāng)使用 `XMLHttpRequest`時(shí),而URL長度不到 `2K`,可以使用 `GET`請求數(shù)據(jù), `GET`相比 `POST`更快速。 * `POST`類型請求要發(fā)送兩個(gè) `TCP`數(shù)據(jù)包。 * 先發(fā)送文件頭。 * 再發(fā)送數(shù)據(jù)。 * `GET`類型請求只需要發(fā)送一個(gè) `TCP`數(shù)據(jù)包。 * 取決于你的 `cookie`數(shù)量。
COOKIE專題
- 減少
COOKIE
的大小。
- 使用無
COOKIE
的域。* 比如圖片 `CSS`等靜態(tài)文件放在靜態(tài)資源服務(wù)器上并配置單獨(dú)域名,客戶端請求靜態(tài)文件的時(shí)候,減少 `COOKIE`反復(fù)傳輸時(shí)對主域名的影響。
DOM優(yōu)化
- 優(yōu)化節(jié)點(diǎn)修改。
* 使用 `cloneNode`在外部更新節(jié)點(diǎn)然后再通過 `replace`與原始節(jié)點(diǎn)互換。
- 優(yōu)化節(jié)點(diǎn)添加
多個(gè)節(jié)點(diǎn)插入操作,即使在外面設(shè)置節(jié)點(diǎn)的元素和風(fēng)格再插入,由于多個(gè)節(jié)點(diǎn)還是會引發(fā)多次reflow。
* 優(yōu)化的方法是創(chuàng)建 `DocumentFragment`,在其中插入節(jié)點(diǎn)后再添加到頁面。 * 如 `JQuery`中所有的添加節(jié)點(diǎn)的操作如 `append`,都是最終調(diào)用 `DocumentFragment`來實(shí)現(xiàn)的, <pre> `createSafeFragment(document) { var list = nodeNames.split( "|" ), safeFrag = document.createDocumentFragment();` </pre>
- 優(yōu)化
CSS
樣式轉(zhuǎn)換。如果需要?jiǎng)討B(tài)更改CSS樣式,盡量采用觸發(fā)reflow次數(shù)較少的方式。
* 如以下代碼逐條更改元素的幾何屬性,理論上會觸發(fā)多次 `reflow`。 <pre> ` element.style.fontWeight = 'bold' ; element.style.marginLeft= '30px' ; element.style.marginRight = '30px' ;`</pre> * 可以通過直接設(shè)置元素的 `className`直接設(shè)置,只會觸發(fā)一次 `reflow`。
- 減少
DOM
元素?cái)?shù)量* 在 `console`中執(zhí)行命令查看 `DOM`元素?cái)?shù)量。 <pre> ` `document.getElementsByTagName( '*' ).length``</pre> * 正常頁面的 `DOM`元素?cái)?shù)量一般不應(yīng)該超過 `1000`。 * `DOM`元素過多會使 `DOM`元素查詢效率,樣式表匹配效率降低,是頁面性能最主要的瓶頸之一。
DOM
操作優(yōu)化。* `DOM`操作性能問題主要有以下原因。 * `DOM`元素過多導(dǎo)致元素定位緩慢。 * 大量的 `DOM`接口調(diào)用。 * `JAVASCRIPT`和 `DOM`之間的交互需要通過函數(shù) `API`接口來完成,造成延時(shí),尤其是在循環(huán)語句中。 * `DOM`操作觸發(fā)頻繁的 `reflow(layout)`和 `repaint`。 * `layout`發(fā)生在 `repaint`之前,所以layout相對來說會造成更多性能損耗。 * `reflow(layout)`就是計(jì)算頁面元素的幾何信息。 * `repaint`就是繪制頁面元素。 * 對 `DOM`進(jìn)行操作會導(dǎo)致瀏覽器執(zhí)行回流 `reflow`。 * 解決方案。 * 純 `JAVASCRIPT`執(zhí)行時(shí)間是很短的。 * 最小化 `DOM`訪問次數(shù),盡可能在js端執(zhí)行。 * 如果需要多次訪問某個(gè) `DOM`節(jié)點(diǎn),請使用局部變量存儲對它的引用。 * 謹(jǐn)慎處理 `HTML`集合( `HTML`集合實(shí)時(shí)連系底層文檔),把集合的長度緩存到一個(gè)變量中,并在迭代中使用它,如果需要經(jīng)常操作集合,建議把它拷貝到一個(gè)數(shù)組中。 * 如果可能的話,使用速度更快的API,比如 `querySelectorAll`和 `firstElementChild`。 * 要留意重繪和重排。 * 批量修改樣式時(shí), `離線`操作 `DOM`樹。 * 使用緩存,并減少訪問布局的次數(shù)。 * 動畫中使用絕對定位,使用拖放代理。 * 使用事件委托來減少事件處理器的數(shù)量。
- 優(yōu)化
DOM
交互在
JAVASCRIPT
中,DOM
操作和交互要消耗大量時(shí)間,因?yàn)樗鼈兺枰匦落秩菊麄€(gè)頁面或者某一個(gè)部分。* 最小化 `現(xiàn)場更新`。 * 當(dāng)需要訪問的 `DOM`部分已經(jīng)已經(jīng)被渲染為頁面中的一部分,那么 `DOM`操作和交互的過程就是再進(jìn)行一次 `現(xiàn)場更新`。 * `現(xiàn)場更新`是需要針對 `現(xiàn)場`(相關(guān)顯示頁面的部分結(jié)構(gòu))立即進(jìn)行更新,每一個(gè)更改(不管是插入單個(gè)字符還是移除整個(gè)片段),都有一個(gè)性能損耗。 * 現(xiàn)場更新進(jìn)行的越多,代碼完成執(zhí)行所花的時(shí)間也越長。 * 多使用 `innerHTML`。 * 有兩種在頁面上創(chuàng)建 `DOM`節(jié)點(diǎn)的方法: * 使用諸如 `createElement()`和 `appendChild()`之類的`DOM`方法。 * 使用 `innerHTML`。 * 當(dāng)使用 `innerHTML`設(shè)置為某個(gè)值時(shí),后臺會創(chuàng)建一個(gè) `HTML`解釋器,然后使用內(nèi)部的 `DOM`調(diào)用來創(chuàng)建 `DOM`結(jié)構(gòu),而非基于 `JAVASCRIPT`的 `DOM`調(diào)用。由于內(nèi)部方法是編譯好的而非解釋執(zhí)行,故執(zhí)行的更快。
- 回流
reflow
。* 發(fā)生場景。 * 改變窗體大小。 * 更改字體。 * 添加移除stylesheet塊。 * 內(nèi)容改變哪怕是輸入框輸入文字。 * CSS虛類被觸發(fā)如 :hover。 * 更改元素的className。 * 當(dāng)對DOM節(jié)點(diǎn)執(zhí)行新增或者刪除操作或內(nèi)容更改時(shí)。 * 動態(tài)設(shè)置一個(gè)style樣式時(shí)(比如element.style.width="10px")。 * 當(dāng)獲取一個(gè)必須經(jīng)過計(jì)算的尺寸值時(shí),比如訪問offsetWidth、clientHeight或者其他需要經(jīng)過計(jì)算的CSS值。 * 解決問題的關(guān)鍵,就是限制通過DOM操作所引發(fā)回流的次數(shù)。 * 在對當(dāng)前DOM進(jìn)行操作之前,盡可能多的做一些準(zhǔn)備工作,保證N次創(chuàng)建,1次寫入。 * 在對DOM操作之前,把要操作的元素,先從當(dāng)前DOM結(jié)構(gòu)中刪除: * 通過removeChild()或者replaceChild()實(shí)現(xiàn)真正意義上的刪除。 * 設(shè)置該元素的display樣式為“none”。 * 每次修改元素的style屬性都會觸發(fā)回流操作。 <pre> `element.style.backgroundColor = "blue";` </pre> * 使用更改 `className`的方式替換 `style.xxx=xxx`的方式。 * 使用 `style.cssText = '';`一次寫入樣式。 * 避免設(shè)置過多的行內(nèi)樣式。 * 添加的結(jié)構(gòu)外元素盡量設(shè)置它們的位置為 `fixed`或 `absolute`。 * 避免使用表格來布局。 * 避免在 `CSS`中使用 `JavaScript expressions(IE only)`。 * 將獲取的 `DOM`數(shù)據(jù)緩存起來。這種方法,對獲取那些會觸發(fā)回流操作的屬性(比如 `offsetWidth`等)尤為重要。 * 當(dāng)對HTMLCollection對象進(jìn)行操作時(shí),應(yīng)該將訪問的次數(shù)盡可能的降至最低,最簡單的,你可以將length屬性緩存在一個(gè)本地變量中,這樣就能大幅度的提高循環(huán)的效率。
eval優(yōu)化
- 避免
eval
:* `eval`會在時(shí)間方面帶來一些效率,但也有很多缺點(diǎn)。 * `eval`會導(dǎo)致代碼看起來更臟。 * `eval`會需要消耗大量時(shí)間。 * `eval`會逃過大多數(shù)壓縮工具的壓縮。
HTML優(yōu)化
- 插入
HTML
。* `JavaScript`中使用 `document.write`生成頁面內(nèi)容會效率較低,可以找一個(gè)容器元素,比如指定一個(gè) `div`,并使用 `innerHTML`來將 `HTML`代碼插入到頁面中。
- 避免空的
src
和href
。* 當(dāng) `link`標(biāo)簽的 `href`屬性為空、 `script`標(biāo)簽的 `src`屬性為空的時(shí)候,瀏覽器渲染的時(shí)候會把當(dāng)前頁面的 `URL`作為它們的屬性值,從而把頁面的內(nèi)容加載進(jìn)來作為它們的值。
- 為文件頭指定
Expires
。* 使內(nèi)容具有緩存性,避免了接下來的頁面訪問中不必要的HTTP請求。
- 重構(gòu)HTML,把重要內(nèi)容的優(yōu)先級提高。
- Post-load(次要加載)不是必須的資源。
- 利用預(yù)加載優(yōu)化資源。
- 合理架構(gòu),使DOM結(jié)構(gòu)盡量簡單。
- 利用
LocalStorage
合理緩存資源。
- 盡量避免CSS表達(dá)式和濾鏡。
- 嘗試使用defer方式加載Js腳本。
- 新特性:will-change,把即將發(fā)生的改變預(yù)先告訴瀏覽器。
- 新特性Beacon,不堵塞隊(duì)列的異步數(shù)據(jù)發(fā)送。
- 不同之處:網(wǎng)絡(luò)緩慢,緩存更小,不令人滿意的瀏覽器處理機(jī)制。
- 盡量多地緩存文件。
- 使用HTML5 Web Workers來允許多線程工作。
- 為不同的Viewports設(shè)置不同大小的Content。
- 正確設(shè)置可Tap的目標(biāo)的大小。
- 使用響應(yīng)式圖片。
- 支持新接口協(xié)議(如HTTP2)。
- 未來的緩存離線機(jī)制:Service Workers。
- 未來的資源優(yōu)化Resource Hints(preconnect, preload, 和prerender)。
- 使用Server-sent Events。
- 設(shè)置一個(gè)Meta Viewport。
JIT與 GC優(yōu)化
untyped
(無類型)。* `JAVASCRIPT`是個(gè)無類型的語言,這導(dǎo)致了如 `x=y+z`這種表達(dá)式可以有很多含義。 * `y`, `z`是數(shù)字,則 `+`表示加法。 * `y`, `z`是字符串,則 `+`表示字符串連接。 而JS引擎內(nèi)部則使用“ `細(xì)粒度`”的類型,比如: * 32-bit* integer。 1. 64-bit* floating-point。 這就要求js類型-js引擎類型,需要做“boxed/unboxed(裝箱/解箱)”,在處理一次 `x=y+z`這種計(jì)算,需要經(jīng)過的步驟如下。 2. 從內(nèi)存,讀取 `x=y+z`的操作符。 3. 從內(nèi)存,讀取 `y`, `z`。 4. 檢查y,z類型,確定操作的行為。 5. `unbox y,z`。 6. 執(zhí)行操作符的行為。 7. `box x`。 8. 把 `x`寫入內(nèi)存。 只有第 `5`步驟是真正有效的操作,其他步驟都是為第 `5`步驟做準(zhǔn)備/收尾, `JAVASCRIPT`的 `untyped`特性很好用,但也為此付出了很大的性能代價(jià)。
JIT
。* 先看看 `JIT`對 `untyped`的優(yōu)化,在 `JIT`下,執(zhí)行 `x=y+z`流程。 1. 從內(nèi)存,讀取 `x=y+z`的操作符。 2. 從內(nèi)存,讀取 `y`, `z`。 3. 檢查 `y`, `z`類型,確定操作的行為。 4. `unbox y,z`。 5. 執(zhí)行 操作符 的行為。 6. `box x`。 7. 把 `x`寫入內(nèi)存。 * 新引擎還對“對象屬性”訪問做了優(yōu)化,解決方案叫 `inline caching`,簡稱: `IC`。簡單的說,就是做 `cache`。但如果當(dāng) `list`很大時(shí),這種方案反而影響效率。
Type-specializing JIT
Type-specializing JIT
引擎用來處理typed
類型(聲明類型)變量,但JAVASCRIPT
都是untype
類型的。* `Type-specializing JIT`的解決方案是: * 先通過掃描,監(jiān)測類型。 * 通過編譯優(yōu)化(優(yōu)化對象不僅僅只是“類型”,還包括對JS代碼的優(yōu)化,但核心是類型優(yōu)化),生成類型變量。 * 再做后續(xù)計(jì)算。 * `Type-specializing JIT`的執(zhí)行 `x=y+z`流程: * * 從內(nèi)存,讀取 `x=y+z`的操作符。 * 從內(nèi)存,讀取 `y`, `z`。 * 檢查 `y`, `z`類型,確定操作的行為。 * `unbox y,z`。 * 執(zhí)行操作符的行為。 * `box x`。 * 把 `x`寫入內(nèi)存。 <pre> `代價(jià)是:` </pre> * 前置的掃描類型 * 編譯優(yōu)化。 <pre> `所以·Type-specializing JIT·的應(yīng)用是有選擇性,選擇使用這個(gè)引擎的場景包括:`</pre> * 熱點(diǎn)代碼。 * 通過啟發(fā)式算法估算出來的有價(jià)值的代碼。 <pre> `另外,有2點(diǎn)也需要注意:`</pre> * 當(dāng)變量類型 發(fā)生變化時(shí),引擎有2種處理方式: * 少量變更,重編譯,再執(zhí)行。 * 大量變更,交給JIT執(zhí)行。 * `數(shù)組`, `object properties`, 閉包變量 不在優(yōu)化范疇之列。
js載入優(yōu)化
- 加快JavaScript裝入速度的工具:
* Lab.js * 借助LAB.js(裝入和阻止JavaScript),你就可以并行裝入JavaScript文件,加快總的裝入過程。此外,你還可以為需要裝入的腳本設(shè)置某個(gè)順序,那樣就能確保依賴關(guān)系的完整性。此外,開發(fā)者聲稱其網(wǎng)站上的速度提升了2倍。
- 使用適當(dāng)?shù)腃DN:
* 現(xiàn)在許多網(wǎng)頁使用內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)。它可以改進(jìn)你的緩存機(jī)制,因?yàn)槊總€(gè)人都可以使用它。它還能為你節(jié)省一些帶寬。你很容易使用ping檢測或使用Firebug調(diào)試那些服務(wù)器,以便搞清可以從哪些方面加快數(shù)據(jù)的速度。選擇CDN時(shí),要照顧到你網(wǎng)站那些訪客的位置。記得盡可能使用公共存儲庫。
- 網(wǎng)頁末尾裝入JavaScript:
* 也可以在頭部分放置需要裝入的一些JavaScript,但是前提是它以異步方式裝入。
- 異步裝入跟蹤代碼:
腳本加載與解析會阻塞HTML渲染,可以通過異步加載方式來避免渲染阻塞,步加載的方式很多,比較通用的方法如下。
<pre> `var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXXXX-XX']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/JavaScript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); })();` </pre>
或者
<pre> `function loadjs (script_filename){
var script = document.createElement( 'script' );
script.setAttribute( 'type' , 'text/javascript' );
script.setAttribute( 'src' , script_filename);
script.setAttribute( 'id' , 'script-id' );scriptElement = document.getElementById( 'script-id' ); if (scriptElement){ document.getElementsByTagName( 'head' )[0].removeChild(scriptElement); } document.getElementsByTagName( 'head' )[0].appendChild(script); } var script = 'scripts/alert.js' ; loadjs(script);` </pre>
- 把你的JavaScript打包成PNG文件
* 將JavaScript/css數(shù)據(jù)打包成PNG文件。之后進(jìn)行拆包,只要使用畫布API的getImageData()。可以在不縮小數(shù)據(jù)的情況下,多壓縮35%左右。而且是無損壓縮,對比較龐大的腳本來說,在圖片指向畫布、讀取像素的過程中,你會覺得有“一段”裝入時(shí)間。
- 設(shè)置Cache-Control和Expires頭
通過Cache-Control和Expires頭可以將腳本文件緩存在客戶端或者代理服務(wù)器上,可以減少腳本下載的時(shí)間。 > Expires格式: > <pre> ` Expires = "Expires" ":" HTTP-date Expires: Thu, 01 Dec 1994 16:00:00 GMT Note: if a response includes a Cache-Control field with the max-age directive that directive overrides the Expires field.`</pre> > Cache-Control格式: > <pre> `Cache-Control = "Cache-Control" ":" 1#cache-directive Cache-Control: public` </pre>
具體的標(biāo)準(zhǔn)定義可以參考http1.1中的定義,簡單來說Expires控制過期時(shí)間是多久,Cache-Control控制什么地方可以緩存 。
with優(yōu)化
- 盡可能地少用
with
語句,因?yàn)樗鼤黾?with
語句以外的數(shù)據(jù)的訪問代價(jià)。
- 避免使用
with
<pre> `> `with`語句將一個(gè)新的可變對象推入作用域鏈的頭部,函數(shù)的所有局部變量現(xiàn)在處于第二個(gè)作用域鏈對象中,從而使局部變量的訪問代價(jià)提高。` </pre>
變量專題
- 全局變量
* 當(dāng)一個(gè)變量被定義在全局作用域中,默認(rèn)情況下 `JAVASCRIPT`引擎就不會將其回收銷毀。如此該變量就會一直存在于老生代堆內(nèi)存中,直到頁面被關(guān)閉。 * `全局變量`缺點(diǎn)。 * 使變量不易被回收。 * 多人協(xié)作時(shí)容易產(chǎn)生混淆。 * 在作用域鏈中容易被干擾。 * 可以通過包裝函數(shù)來處理 `全局變量`。
- 局部變量。
* 盡量選用局部變量而不是全局變量。 * 局部變量的訪問速度要比全局變量的訪問速度更快,因?yàn)槿肿兞科鋵?shí)是 `window`對象的成員,而局部變量是放在函數(shù)的棧里的。
- 手工解除變量引用
* 在業(yè)務(wù)代碼中,一個(gè)變量已經(jīng)確定不再需要了,那么就可以手工解除變量引用,以使其被回收。
- 變量查找優(yōu)化。
* 變量聲明帶上 `var`,如果聲明變量忘記了 `var`,那么 `JAVASCRIPT`引擎將會遍歷整個(gè)作用域查找這個(gè)變量,結(jié)果不管找到與否,都會造成性能損耗。 * 如果在上級作用域找到了這個(gè)變量,上級作用域變量的內(nèi)容將被無聲的改寫,導(dǎo)致莫名奇妙的錯(cuò)誤發(fā)生。 * 如果在上級作用域沒有找到該變量,這個(gè)變量將自動被聲明為全局變量,然而卻都找不到這個(gè)全局變量的定義。 * 慎用全局變量。 * 全局變量需要搜索更長的作用域鏈。 * 全局變量的生命周期比局部變量長,不利于內(nèi)存釋放。 * 過多的全局變量容易造成混淆,增大產(chǎn)生bug的可能性。 > * 具有相同作用域變量通過一個(gè)var聲明。 <pre> ` jQuery.extend = jQuery.fn.extend = function () { var options, name, src, copy, copyIsArray, clone,target = arguments[0] || {}, i = 1, length = arguments.length, deep = false ; }`</pre> * 緩存重復(fù)使用的全局變量。 * 全局變量要比局部變量需要搜索的作用域長 * 重復(fù)調(diào)用的方法也可以通過局部緩存來提速 * 該項(xiàng)優(yōu)化在IE上體現(xiàn)比較明顯
- 善用回調(diào)。
* 除了使用閉包進(jìn)行內(nèi)部變量訪問,我們還可以使用現(xiàn)在十分流行的回調(diào)函數(shù)來進(jìn)行業(yè)務(wù)處理。 <pre> ` function getData(callback) { var data = 'some big data'; callback(null, data); } getData(function(err, data) { console.log(data); });`</pre> * 回調(diào)函數(shù)是一種后續(xù)傳遞風(fēng)格( `Continuation Passing Style`, `CPS`)的技術(shù),這種風(fēng)格的程序編寫將函數(shù)的業(yè)務(wù)重點(diǎn)從返回值轉(zhuǎn)移到回調(diào)函數(shù)中去。而且其相比閉包的好處也有很多。 * 如果傳入的參數(shù)是基礎(chǔ)類型(如字符串、數(shù)值),回調(diào)函數(shù)中傳入的形參就會是復(fù)制值,業(yè)務(wù)代碼使用完畢以后,更容易被回收。 * 通過回調(diào),我們除了可以完成同步的請求外,還可以用在異步編程中,這也就是現(xiàn)在非常流行的一種編寫風(fēng)格。 * 回調(diào)函數(shù)自身通常也是臨時(shí)的匿名函數(shù),一旦請求函數(shù)執(zhí)行完畢,回調(diào)函數(shù)自身的引用就會被解除,自身也得到回收。
常規(guī)優(yōu)化
- 傳遞方法取代方法字符串
一些方法例如 `setTimeout()`、 `setInterval()`,接受 `字符串`或者 `方法實(shí)例`作為參數(shù)。直接傳遞方法對象作為參數(shù)來避免對字符串的二次解析。 * * 傳遞方法 <pre> `setTimeout(test, 1);` </pre> * 傳遞方法字符串
- 使用原始操作代替方法調(diào)用
方法調(diào)用一般封裝了原始操作,在性能要求高的邏輯中,可以使用原始操作代替方法調(diào)用來提高性能。 * 原始操作 <pre> ` var min = a<b?a:b;`</pre> * 方法實(shí)例
- 定時(shí)器
如果針對的是不斷運(yùn)行的代碼,不應(yīng)該使用 `setTimeout`,而應(yīng)該是用 `setInterval`。 `setTimeout`每次要重新設(shè)置一個(gè)定時(shí)器。
- 避免雙重解釋
當(dāng)
JAVASCRIPT
代碼想解析JAVASCRIPT
代碼時(shí)就會存在雙重解釋懲罰,雙重解釋一般在使用eval
函數(shù)、new Function
構(gòu)造函數(shù)和setTimeout
傳一個(gè)字符串時(shí)等情況下會遇到,如。<pre>
eval("alert('hello world');"); var sayHi = new Function("alert('hello world');"); setTimeout("alert('hello world');", 100);
</pre>
上述
alert('hello world');
語句包含在字符串中,即在JS代碼運(yùn)行的同時(shí)必須新啟運(yùn)一個(gè)解析器來解析新的代碼,而實(shí)例化一個(gè)新的解析器有很大的性能損耗。
<pre> `我們看看下面的例子:
var sum, num1 = 1, num2 = 2; /**效率低**/ for(var i = 0; i < 10000; i++){ var func = new Function("sum+=num1;num1+=num2;num2++;"); func(); //eval("sum+=num1;num1+=num2;num2++;"); } /**效率高**/ for(var i = 0; i < 10000; i++){ sum+=num1; num1+=num2; num2++; }` </pre>
第一種情況我們是使用了new Function來進(jìn)行雙重解釋,而第二種是避免了雙重解釋。
- 原生方法更快
* 只要有可能,使用原生方法而不是自已用JS重寫。原生方法是用諸如C/C++之類的編譯型語言寫出來的,要比JS的快多了。
- 最小化語句數(shù)
JS代碼中的語句數(shù)量也會影響所執(zhí)行的操作的速度,完成多個(gè)操作的單個(gè)語句要比完成單個(gè)操作的多個(gè)語句塊快。故要找出可以組合在一起的語句,以減來整體的執(zhí)行時(shí)間。這里列舉幾種模式 * 多個(gè)變量聲明 <pre> `/**不提倡**/ var i = 1; var j = "hello"; var arr = [1,2,3]; var now = new Date(); /**提倡**/ var i = 1, j = "hello", arr = [1,2,3], now = new Date();` </pre> * 插入迭代值 <pre> `/**不提倡**/ var name = values[i]; i++; /**提倡**/ var name = values[i++];` </pre> * 使用數(shù)組和對象字面量,避免使用構(gòu)造函數(shù)Array(),Object() <pre> `/**不提倡**/ var a = new Array(); a[0] = 1; a[1] = "hello"; a[2] = 45; var o = new Obejct(); o.name = "bill"; o.age = 13; /**提倡**/ var a = [1, "hello", 45]; var o = { name : "bill", age : 13 };` </pre>
- 避免使用屬性訪問方法
* JavaScript不需要屬性訪問方法,因?yàn)樗械膶傩远际峭獠靠梢姷摹? * 添加屬性訪問方法只是增加了一層重定向 ,對于訪問控制沒有意義。 <pre> `使用屬性訪問方法示例 function Car() { this .m_tireSize = 17; this .m_maxSpeed = 250; this .GetTireSize = Car_get_tireSize; this .SetTireSize = Car_put_tireSize; } function Car_get_tireSize() { return this .m_tireSize; } function Car_put_tireSize(value) { this .m_tireSize = value; } var ooCar = new Car(); var iTireSize = ooCar.GetTireSize(); ooCar.SetTireSize(iTireSize + 1); 直接訪問屬性示例 function Car() { this .m_tireSize = 17; this .m_maxSpeed = 250; } var perfCar = new Car(); var iTireSize = perfCar.m_tireSize; perfCar.m_tireSize = iTireSize + 1;` </pre>
- 減少使用元素位置操作
* 一般瀏覽器都會使用增量reflow的方式將需要reflow的操作積累到一定程度然后再一起觸發(fā),但是如果腳本中要獲取以下屬性,那么積累的reflow將會馬上執(zhí)行,已得到準(zhǔn)確的位置信息。
代碼壓縮
- 代碼壓縮工具
> 精簡代碼就是將代碼中的 `空格`和 `注釋`去除,也有更進(jìn)一步的會對變量名稱 `混淆`、 `精簡`。根據(jù)統(tǒng)計(jì)精簡后文件大小會平均減少 `21%`,即使 `Gzip`之后文件也會減少 `5%`。 * YUICompressor * Dean Edwards Packer * JSMin * GZip壓縮 * `GZip`縮短在瀏覽器和服務(wù)器之間傳送數(shù)據(jù)的時(shí)間,縮短時(shí)間后得到標(biāo)題是 `Accept-Encoding`: `gzip`, `deflate`的一個(gè)文件。不過這種壓縮方法同樣也有缺點(diǎn)。 * 它在服務(wù)器端和客戶端都要占用處理器資源(以便壓縮和解壓縮)。 * 占用磁盤空間。 * `Gzip`通常可以減少70%網(wǎng)頁內(nèi)容的大小,包括腳本、樣式表、圖片等任何一個(gè)文本類型的響應(yīng),包括 `XML`和 `JSON`。 `Gzip`比 `deflate`更高效,主流服務(wù)器都有相應(yīng)的壓縮支持模塊。 * `Gzip`的工作流程為 * 客戶端在請求 `Accept-Encoding`中聲明可以支持 `Gzip`。 * 服務(wù)器將請求文檔壓縮,并在 `Content-Encoding`中聲明該回復(fù)為 `Gzip`格式。 * 客戶端收到之后按照 `Gzip`解壓縮。 * Closure compiler
代碼優(yōu)化
- 優(yōu)化原則:
- JS優(yōu)化總是出現(xiàn)在大規(guī)模循環(huán)的地方:
<pre> `這倒不是說循環(huán)本身有性能問題,而是循環(huán)會迅速放大可能存在的性能問題,所以第二原則就是以大規(guī)模循環(huán)體為最主要優(yōu)化對象。` </pre> 以下的優(yōu)化原則,只在大規(guī)模循環(huán)中才有意義,在循環(huán)體之外做此類優(yōu)化基本上是沒有意義的。 目前絕大多數(shù)JS引擎都是解釋執(zhí)行的,而解釋執(zhí)行的情況下,在所有操作中,函數(shù)調(diào)用的效率是較低的。此外,過深的prototype繼承鏈或者多級引用也會降低效率。JScript中,10級引用的開銷大體是一次空函數(shù)調(diào)用開銷的1/2。這兩者的開銷都遠(yuǎn)遠(yuǎn)大于簡單操作(如四則運(yùn)算)。
- 盡量避免過多的引用層級和不必要的多次方法調(diào)用:
<pre> `特別要注意的是,有些情況下看似是屬性訪問,實(shí)際上是方法調(diào)用。例如所有DOM的屬性,實(shí)際上都是方法。在遍歷一個(gè)NodeList的時(shí)候,循環(huán) 條件對于nodes.length的訪問,看似屬性讀取,實(shí)際上是等價(jià)于函數(shù)調(diào)用的。而且IE DOM的實(shí)現(xiàn)上,childNodes.length每次是要通過內(nèi)部遍歷重新計(jì)數(shù)的。(My god,但是這是真的!因?yàn)槲覝y過,childNodes.length的訪問時(shí)間與childNodes.length的值成正比!)這非常耗費(fèi)。所以 預(yù)先把nodes.length保存到j(luò)s變量,當(dāng)然可以提高遍歷的性能。` </pre> 同樣是函數(shù)調(diào)用,用戶自定義函數(shù)的效率又遠(yuǎn)遠(yuǎn)低于語言內(nèi)建函數(shù),因?yàn)楹笳呤菍σ姹镜胤椒ǖ陌b,而引擎通常是c,c++,java寫的。進(jìn)一步,同樣的功能,語言內(nèi)建構(gòu)造的開銷通常又比內(nèi)建函數(shù)調(diào)用要效率高,因?yàn)榍罢咴贘S代碼的parse階段就可以確定和優(yōu)化。
- 盡量使用語言本身的構(gòu)造和內(nèi)建函數(shù):
動畫優(yōu)化
- 動畫效果在缺少硬件加速支持的情況下反應(yīng)緩慢,例如手機(jī)客戶端。
* 特效應(yīng)該只在確實(shí)能改善用戶體驗(yàn)時(shí)才使用,而不應(yīng)用于炫耀或者彌補(bǔ)功能與可用性上的缺陷。 * 至少要給用戶一個(gè)選擇可以禁用動畫效果。 * 設(shè)置動畫元素為absolute或fixed。 * `position: static`或 `position: relative`元素應(yīng)用動畫效果會造成頻繁的 `reflow`。 * `position: absolute`或 `position: fixed`的元素應(yīng)用動畫效果只需要 `repaint`。 * 使用一個(gè) `timer`完成多個(gè)元素動畫。 * `setInterval`和 `setTimeout`是兩個(gè)常用的實(shí)現(xiàn)動畫的接口,用以間隔更新元素的風(fēng)格與布局。。 * 動畫效果的幀率最優(yōu)化的情況是使用一個(gè) `timer`完成多個(gè)對象的動畫效果,其原因在于多個(gè) `timer`的調(diào)用本身就會損耗一定性能。 <pre> `setInterval(function() { animateFirst(''); }, 10); setInterval(function() { animateSecond(''); }, 10); 使用同一個(gè)`timer`。` </pre>
- 以腳本為基礎(chǔ)的動畫,由瀏覽器控制動畫的更新頻率。
對象專題
- 減少不必要的對象創(chuàng)建:
* 創(chuàng)建對象本身對性能影響并不大,但由于 `JAVASCRIPT`的垃圾回收調(diào)度算法,導(dǎo)致隨著對象個(gè)數(shù)的增加,性能會開始嚴(yán)重下降(復(fù)雜度 `O(n^2)`)。 * 如常見的字符串拼接問題,單純的多次創(chuàng)建字符串對象其實(shí)根本不是降低性能的主要原因,而是是在對象創(chuàng)建期間的無謂的垃圾回收的開銷。而 `Array.join`的方式,不會創(chuàng)建中間字符串對象,因此就減少了垃圾回收的開銷。 * 復(fù)雜的 `JAVASCRIPT`對象,其創(chuàng)建時(shí)時(shí)間和空間的開銷都很大,應(yīng)該盡量考慮采用緩存。 * 盡量作用 `JSON`格式來創(chuàng)建對象,而不是 `var obj=new Object()`方法。前者是直接復(fù)制,而后者需要調(diào)用構(gòu)造器。
- 對象查找
* 避免對象的嵌套查詢,因?yàn)?`JAVASCRIPT`的解釋性, `a.b.c.d.e`嵌套對象,需要進(jìn)行 `4`次查詢,嵌套的對象成員會明顯影響性能。 * 如果出現(xiàn)嵌套對象,可以利用局部變量,把它放入一個(gè)臨時(shí)的地方進(jìn)行查詢。
- 對象屬性
* 訪問對象屬性消耗性能過程( `JAVASCRIPT`對象存儲)。 * 先從本地變量表找到 `對象`。 * 然后遍歷 `屬性`。 * 如果在 `當(dāng)前對象`的 `屬性列表`里沒找到。 * 繼續(xù)從 `prototype`向上查找。 * 且不能直接索引,只能遍歷。
服務(wù)端優(yōu)化
- 避免404。
* 更改404錯(cuò)誤響應(yīng)頁面可以改進(jìn)用戶體驗(yàn),但是同樣也會浪費(fèi)服務(wù)器資源。 * 指向外部 `JAVASCRIPT`的鏈接出現(xiàn)問題并返回404代碼。 * 這種加載會破壞并行加載。 * 其次瀏覽器會把試圖在返回的404響應(yīng)內(nèi)容中找到可能有用的部分當(dāng)作JavaScript代碼來執(zhí)行。
- 刪除重復(fù)的
JAVASCRIPT
和CSS
。* 重復(fù)調(diào)用腳本缺點(diǎn)。 * 增加額外的HTTP請求。 * 多次運(yùn)算也會浪費(fèi)時(shí)間。在IE和Firefox中不管腳本是否可緩存,它們都存在重復(fù)運(yùn)算 `JAVASCRIPT`的問題。
ETags
配置Entity
標(biāo)簽。* `ETags`用來判斷瀏覽器緩存里的元素是否和原來服務(wù)器上的一致。 * 與 `last-modified date`相比更靈活。
- 權(quán)衡DNS查找次數(shù)
* 減少主機(jī)名可以節(jié)省響應(yīng)時(shí)間。但同時(shí)也會減少頁面中并行下載的數(shù)量。 * `IE`瀏覽器在同一時(shí)刻只能從同一域名下載兩個(gè)文件。當(dāng)在一個(gè)頁面顯示多張圖片時(shí), `IE`用戶的圖片下載速度就會受到影響。
- 通過Keep-alive機(jī)制減少TCP連接。
- 通過CDN減少延時(shí)。
- 平行處理請求(參考BigPipe)。
- 通過合并文件或者Image Sprites減少HTTP請求。
- 減少重定向( HTTP 301和40x/50x)。
類型轉(zhuǎn)換專題
- 把數(shù)字轉(zhuǎn)換成字符串。
* 應(yīng)用 `""+1`,效率是最高。 * 性能上來說: `""+字符串`> `String()`> `.toString()`> `new String()`。 * `String()`屬于內(nèi)部函數(shù),所以速度很快。 * `.toString()`要查詢原型中的函數(shù),所以速度略慢。 * `new String()`最慢。
- 浮點(diǎn)數(shù)轉(zhuǎn)換成整型。
* 錯(cuò)誤使用使用 `parseInt()`。 * `parseInt()`是用于將 `字符串`轉(zhuǎn)換成 `數(shù)字`,而不是 `浮點(diǎn)數(shù)`和`整型`之間的轉(zhuǎn)換。 * 應(yīng)該使用 `Math.floor()`或者 `Math.round()`。 * `Math`是內(nèi)部對象,所以 `Math.floor()`其實(shí)并沒有多少查詢方法和調(diào)用的時(shí)間,速度是最快的。
邏輯判斷優(yōu)化
switch
語句。* 若有一系列復(fù)雜的 `if-else`語句,可以轉(zhuǎn)換成單個(gè) `switch`語句則可以得到更快的代碼,還可以通過將 `case`語句按照最可能的到最不可能的順序進(jìn)行組織,來進(jìn)一步優(yōu)化。
內(nèi)存專題
JAVASCRIPT
的內(nèi)存回收機(jī)制* 以Google的 `V8`引擎為例,在 `V8`引擎中所有的 `JAVASCRIPT`對象都是通過 `堆`來進(jìn)行內(nèi)存分配的。當(dāng)我們在代碼中 `聲明變量`并 `賦值`時(shí), `V8`引擎就會在 `堆內(nèi)存`中分配一部分給這個(gè) `變量`。如果已申請的 `內(nèi)存`不足以存儲這個(gè) `變量`時(shí), `V8`引擎就會繼續(xù)申請 `內(nèi)存`,直到 `堆`的大小達(dá)到了 `V8`引擎的內(nèi)存上限為止(默認(rèn)情況下, `V8`引擎的 `堆內(nèi)存`的大小上限在 `64位系統(tǒng)`中為 `1464MB`,在 `32位系統(tǒng)`中則為 `732MB`)。 * 另外, `V8`引擎對 `堆內(nèi)存`中的 `JAVASCRIPT`對象進(jìn)行 `分代管理`。 * 新生代。 * 新生代即存活周期較短的 `JAVASCRIPT`對象,如臨時(shí)變量、字符串等 * 老生代。 * 老生代則為經(jīng)過多次垃圾回收仍然存活,存活周期較長的對象,如主控制器、服務(wù)器對象等。
- 垃圾回收算法。
* 垃圾回收算法一直是編程語言的研發(fā)中是否重要的??一環(huán),而 `V8`引擎所使用的垃圾回收算法主要有以下幾種。 * `Scavange`算法:通過復(fù)制的方式進(jìn)行內(nèi)存空間管理,主要用于新生代的內(nèi)存空間; * `Mark-Sweep`算法和 `Mark-Compact`算法:通過標(biāo)記來對堆內(nèi)存進(jìn)行整理和回收,主要用于老生代對象的檢查和回收。
- 對象進(jìn)行回收。
* `引用`。 * 當(dāng)函數(shù)執(zhí)行完畢時(shí),在函數(shù)內(nèi)部所聲明的對象 `不一定`就會被銷毀。 * 引用( `Reference`)是 `JAVASCRIPT`編程中十分重要的一個(gè)機(jī)制。 * * 是指 `代碼對對象的訪問`這一抽象關(guān)系,它與`C/C++`的指針有點(diǎn)相似,但并非同物。引用同時(shí)也是 `JAVASCRIPT`引擎在進(jìn)行 `垃圾回收`中最關(guān)鍵的一個(gè)機(jī)制。 <pre> `var val = 'hello world'; function foo() { return function() { return val; }; } global.bar = foo();` </pre> * 當(dāng)代碼執(zhí)行完畢時(shí),對象 `val`和 `bar()`并沒有被回收釋放, `JAVASCRIPT`代碼中,每個(gè) `變量`作為單獨(dú)一行而不做任何操作, `JAVASCRIPT`引擎都會認(rèn)為這是對 `對象`的訪問行為,存在了對 `對象的引用`。為了保證 `垃圾回收`的行為不影響程序邏輯的運(yùn)行, `JAVASCRIPT`引擎不會把正在使用的 `對象`進(jìn)行回收。所以判斷 `對象`是否正在使用中的標(biāo)準(zhǔn),就是是否仍然存在對該 `對象`的 `引用`。
-
JAVASCRIPT
的引用
是可以進(jìn)行轉(zhuǎn)移
的,那么就有可能出現(xiàn)某些引用被帶到了全局作用域,但事實(shí)上在業(yè)務(wù)邏輯里已經(jīng)不需要對其進(jìn)行訪問了,這個(gè)時(shí)候就應(yīng)該被回收,但是JAVASCRIPT
引擎仍會認(rèn)為程序仍然需要它。
IE
下閉包引起跨頁面內(nèi)存泄露。
JAVASCRIPT
的內(nèi)存泄露處理* 給 `DOM`對象添加的屬性是一個(gè)對象的引用。 <pre> `var MyObject = {}; document.getElementByIdx_x('myDiv').myProp = MyObject; 解決方法:在window.onunload事件中寫上: document.getElementByIdx_x('myDiv').myProp = null;` </pre> * DOM對象與JS對象相互引用。 <pre> `function Encapsulator(element) { this.elementReference = element; element.myProp = this; } new Encapsulator(document.getElementByIdx_x('myDiv')); 解決方法:在onunload事件中寫上: document.getElementByIdx_x('myDiv').myProp = null;` </pre> * 給DOM對象用attachEvent綁定事件。 <pre> `function doClick() {} element.attachEvent("onclick", doClick); 解決方法:在onunload事件中寫上: element.detachEvent('onclick', doClick);` </pre> * 從外到內(nèi)執(zhí)行appendChild。這時(shí)即使調(diào)用removeChild也無法釋放。 <pre> `var parentDiv = document.createElement_x("div"); var childDiv = document.createElement_x("div"); document.body.appendChild(parentDiv); parentDiv.appendChild(childDiv); 解決方法:從內(nèi)到外執(zhí)行appendChild: var parentDiv = document.createElement_x("div"); var childDiv = document.createElement_x("div"); parentDiv.appendChild(childDiv); document.body.appendChild(parentDiv);` </pre> * 反復(fù)重寫同一個(gè)屬性會造成內(nèi)存大量占用(但關(guān)閉IE后內(nèi)存會被釋放)。 <pre> `for(i = 0; i < 5000; i++) { hostElement.text = "asdfasdfasdf"; }` </pre>
內(nèi)存
不是緩存
。* 不要輕易將 `內(nèi)存`當(dāng)作 `緩存`使用。 * 如果是很重要的資源,請不要直接放在 `內(nèi)存`中,或者制定 `過期機(jī)制`,自動銷毀 `過期緩存`。
CollectGarbage
。* `CollectGarbage`是 `IE`的一個(gè)特有屬性,用于釋放內(nèi)存的使用方法,將該變量或引用對象設(shè)置為 `null`或 `delete`然后在進(jìn)行釋放動作,在做 `CollectGarbage`前,要必需清楚的兩個(gè)必備條件:(引用)。 * 一個(gè)對象在其生存的上下文環(huán)境之外,即會失效。 * 一個(gè)全局的對象在沒有被執(zhí)用(引用)的情況下,即會失效
事件優(yōu)化
- 使用事件代理
* 當(dāng)存在多個(gè)元素需要注冊事件時(shí),在每個(gè)元素上綁定事件本身就會對性能有一定損耗。 * 由于DOM Level2事件模 型中所有事件默認(rèn)會傳播到上層文檔對象,可以借助這個(gè)機(jī)制在上層元素注冊一個(gè)統(tǒng)一事件對不同子元素進(jìn)行相應(yīng)處理。
捕獲型事件先發(fā)生。兩種事件流會觸發(fā)DOM中的所有對象,從document對象開始,也在document對象結(jié)束。
<pre>
<ul id="parent-list"> <li id="post-1">Item 1 <li id="post-2">Item 2 <li id="post-3">Item 3 <li id="post-4">Item 4 <li id="post-5">Item 5 <li id="post-6">Item 6 </li></ul> // Get the element, add a click listener... document.getElementById("parent-list").addEventListener("click",function(e) { // e.target is the clicked element! // If it was a list item if(e.target && e.target.nodeName == "LI") { // List item found! Output the ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); } });
</pre>
數(shù)組專題
- 當(dāng)需要使用數(shù)組時(shí),可使用
JSON
格式的語法* 即直接使用如下語法定義數(shù)組: `[parrm,param,param...]`,而不是采用 `new Array(parrm,param,param...)`這種語法。使用 `JSON`格式的語法是引擎直接解釋。而后者則需要調(diào)用 `Array`的構(gòu)造器。
- 如果需要遍歷數(shù)組,應(yīng)該先緩存數(shù)組長度,將數(shù)組長度放入局部變量中,避免多次查詢數(shù)組長度。
* 根據(jù)字符串、數(shù)組的長度進(jìn)行循環(huán),而通常這個(gè)長度是不變的,比如每次查詢 `a.length`,就要額外進(jìn)行一個(gè)操作,而預(yù)先把 `var len=a.length`,則每次循環(huán)就少了一次查詢。
同域跨域
- 避免跳轉(zhuǎn)
* 同域:注意避免反斜杠 “/” 的跳轉(zhuǎn); * 跨域:使用Alias或者mod_rewirte建立CNAME(保存域名與域名之間關(guān)系的DNS記錄)
性能測試工具
- js性能優(yōu)化和內(nèi)存泄露問題及檢測分析工具
* * 性能優(yōu)化ajax工具 `diviefirebug` * [web性能分析工具YSlow] * `performance`性能評估打分,右擊箭頭可看到改進(jìn)建議。 * `stats`緩存狀態(tài)分析,傳輸內(nèi)容分析。 * `components`所有加載內(nèi)容分析,可以查看傳輸速度,找出頁面訪問慢的瓶頸。 * `tools`可以查看js和css,并打印頁面評估報(bào)告。 * 內(nèi)存泄露檢測工具 `sIEve` * `sIEve`是基于 `IE`的內(nèi)存泄露檢測工具,需要下載運(yùn)行,可以查看dom孤立節(jié)點(diǎn)和內(nèi)存泄露及內(nèi)存使用情況。 1. 列出當(dāng)前頁面內(nèi)所有dom節(jié)點(diǎn)的基本信息(html id style 等) 2. 頁面內(nèi)所有dom節(jié)點(diǎn)的高級信息 (內(nèi)存占用,數(shù)量,節(jié)點(diǎn)的引用) 3. 可以查找出頁面中的孤立節(jié)點(diǎn) 4. 可以查找出頁面中的循環(huán)引用 5. 可以查找出頁面中產(chǎn)生內(nèi)存泄露的節(jié)點(diǎn) * 內(nèi)存泄露提示工具 `leak monitor` * `leak monitor`在安裝后,當(dāng)離開一個(gè)頁面時(shí),比如關(guān)閉窗口,如果頁面有內(nèi)存泄露,會彈出一個(gè)文本框進(jìn)行即時(shí)提示。 * 代碼壓縮工具 * YUI壓縮工具 * Dean Edwards Packer * JSMin * `Blink/Webkit`瀏覽器 * 在 `Blink/Webkit`瀏覽器中( `Chrome`, `Safari`, `Opera`),我們可以借助其中的 `Developer Tools`的 `Profiles`工具來對我們的程序進(jìn)行內(nèi)存檢查。
Node.js
中的內(nèi)存檢查* 在 `Node.js`中,我們可以使用 `node-heapdump`和 `node-memwatch`模塊進(jìn)??行內(nèi)存檢查。 <pre> `var heapdump = require('heapdump'); var fs = require('fs'); var path = require('path'); fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid);` </pre> 在業(yè)務(wù)代碼中引入 `node-heapdump`之后,我們需要在某個(gè)運(yùn)行時(shí)期,向 `Node.js`進(jìn)程發(fā)送 `SIGUSR2`信號,讓 `node-heapdump`抓拍一份堆內(nèi)存的快照。 <pre> `$ kill -USR2 (cat app.pid)` </pre>
- 分析瀏覽器提供的Waterfall圖片來思考優(yōu)化入口。
- 新的測試手段(Navigation, Resource, 和User timing。
循環(huán)專題
- 循環(huán)是一種常用的流程控制。
* `JAVASCRIPT`提供了三種循環(huán)。 * `for(;;)`。 * 推薦使用for循環(huán),如果循環(huán)變量遞增或遞減,不要單獨(dú)對循環(huán)變量賦值,而應(yīng)該使用嵌套的 `++`或 `–-`運(yùn)算符。 * 代碼的可讀性對于for循環(huán)的優(yōu)化。 * 用 `-=1`。 * 從大到小的方式循環(huán)(這樣缺點(diǎn)是降低代碼的可讀性)。 * `while()`。 * `for(;;)`、 `while()`循環(huán)的性能基本持平。 * `for(in)`。 * 在這三種循環(huán)中 `for(in)`內(nèi)部實(shí)現(xiàn)是構(gòu)造一個(gè)所有元素的列表,包括 `array`繼承的屬性,然后再開始循環(huán),并且需要查詢hasOwnProperty。所以 `for(in)`相對 `for(;;)`循環(huán)性能要慢。
- 選擇正確的方法
* 避免不必要的屬性查找。 * 訪問 `變量`或 `數(shù)組`是 `O(1)`操作。 * 訪問 `對象`上的 `屬性`是一個(gè) `O(n)`操作。 <pre> `對象上的任何屬性查找都要比訪問變量或數(shù)組花費(fèi)更長時(shí)間,因?yàn)楸仨氃谠玩溨袑碛性撁Q的屬性進(jìn)行一次搜索,即屬性查找越多,執(zhí)行時(shí)間越長。所以針對需要多次用到對象屬性,應(yīng)將其存儲在局部變量。` </pre> * 優(yōu)化循環(huán)。 * 減值迭代。 * 大多數(shù)循環(huán)使用一個(gè)從0開始,增加到某個(gè)特定值的迭代器。在很多情況下,從最大值開始,在循環(huán)中不斷減值的迭代器更加有效。 * 簡化終止條件。 * 由于每次循環(huán)過程都會計(jì)算終止條件,故必須保證它盡可能快,即避免屬性查找或其它O(n)的操作。 * 簡化循環(huán)體。 * 循環(huán)體是執(zhí)行最多的,故要確保其被最大限度地優(yōu)化。確保沒有某些可以被很容易移出循環(huán)的密集計(jì)算。 * 使用后測試循環(huán)。 * 最常用的for和while循環(huán)都是前測試循環(huán),而如do-while循環(huán)可以避免最初終止條件的計(jì)算,因些計(jì)算更快。 * 展開循環(huán)。 * 當(dāng)循環(huán)的次數(shù)確定時(shí),消除循環(huán)并使用多次函數(shù)調(diào)用往往更快。 * 當(dāng)循環(huán)的次數(shù)不確定時(shí),可以使用Duff裝置來優(yōu)化。 * Duff裝置的基本概念是通過計(jì)算迭代的次數(shù)是否為8的倍數(shù)將一個(gè)循環(huán)展開為一系列語句。
- 避免在循環(huán)中使用
try-catch
。* `try-catch-finally`語句在catch語句被執(zhí)行的過程中會動態(tài)構(gòu)造變量插入到當(dāng)前域中,對性能有一定影響。 * 如果需要異常處理機(jī)制,可以將其放在循環(huán)外層使用。 * * 循環(huán)中使用try-catch <pre> `for ( var i = 0; i < 200; i++) { try {} catch (e) {} }` </pre> * 循環(huán)外使用try-catch <pre> `try { for ( var i = 0; i < 200; i++) {} } catch (e) {}` </pre>
- 避免遍歷大量元素:
* 避免對全局 `DOM`元素進(jìn)行遍歷,如果 `parent`已知可以指定 `parent`在特定范圍查詢。 <pre> `var elements = document.getElementsByTagName( '*' ); for (i = 0; i < elements.length; i++) { if (elements[i].hasAttribute( 'selected' )) {} } 如果已知元素存在于一個(gè)較小的范圍內(nèi),` </pre>
原型優(yōu)化
- 通過原型優(yōu)化方法定義。
* 如果一個(gè)方法類型將被頻繁構(gòu)造,通過方法原型從外面定義附加方法,從而避免方法的重復(fù)定義。 * 可以通過外部原型的構(gòu)造方式初始化值類型的變量定義。(這里強(qiáng)調(diào)值類型的原因是,引用類型如果在原型中定義,一個(gè)實(shí)例對引用類型的更改會影響到其他實(shí)例。) * 這條規(guī)則中涉及到 `JAVASCRIPT`中原型的概念,構(gòu)造函數(shù)都有一個(gè) `prototype`屬性,指向另一個(gè)對象。這個(gè)對象的所有屬性和方法,都會被構(gòu)造函數(shù)的實(shí)例繼承。可以把那些不變的屬性和方法,直接定義在 `prototype`對象上。 * 可以通過對象實(shí)例訪問保存在原型中的值。 * 不能通過對象實(shí)例重寫原型中的值。 * 在實(shí)例中添加一個(gè)與實(shí)例原型同名屬性,那該屬性就會屏蔽原型中的屬性。 * 通過delete操作符可以刪除實(shí)例中的屬性。
運(yùn)算符專題
- 使用運(yùn)算符時(shí),盡量使用
+=
,-=
、*=
、\=
等運(yùn)算符號,而不是直接進(jìn)行賦值運(yùn)算。
位運(yùn)算
。* 當(dāng)進(jìn)行數(shù)學(xué)運(yùn)算時(shí) `位運(yùn)算`較快, `位運(yùn)算`操作要比任何 `布爾運(yùn)算`或 `算數(shù)運(yùn)算`快,如 `取模`, `邏輯與`和 `邏輯或`也可以考慮用 `位運(yùn)算`來替換。
重繪專題
- 減少頁面的
重繪
。* 減少頁面 `重繪`雖然本質(zhì)不是 `JAVASCRIPT`優(yōu)化,但 `重繪`往往是由`JAVASCRIPT`引起的,而 `重繪`的情況直接影響頁面性能。 <pre> `var str = "<div>這是一個(gè)測試字符串</div>"; /**效率低**/ var obj = document.getElementsByTagName("body"); for(var i = 0; i < 100; i++){ obj.innerHTML += str + i; } /**效率高**/ var obj = document.getElementsByTagName("body"); var arr = []; for(var i = 0; i < 100; i++){ arr[i] = str + i; } obj.innerHTML = arr.join("");` </pre> 一般影響頁面重繪的不僅僅是innerHTML,如果改變元素的樣式,位置等情況都會觸發(fā)頁面重繪,所以在平時(shí)一定要注意這點(diǎn)。
- 使用HTML5和CSS3的一些新特性。
- 避免在HTML里面縮放圖片。
- 避免使用插件。
- 確保使用正確的字體大小。
- 決定當(dāng)前頁面是不是能被訪問。
字符串專題
- 對字符串進(jìn)行循環(huán)操作。
* 替換、查找等操作,使用正則表達(dá)式。 * 因?yàn)?`JAVASCRIPT`的循環(huán)速度較慢,而正則表達(dá)式的操作是用 `C`寫成的 `API`,性能比較好。
- 字符串的拼接。
* 字符串的拼接在我們開發(fā)中會經(jīng)常遇到,所以我把其放在首位,我們往往習(xí)慣的直接用 `+=`的方式來拼接字符串,其實(shí)這種拼接的方式效率非常的低,我們可以用一種巧妙的方法來實(shí)現(xiàn)字符串的拼接,那就是利用數(shù)組的 `join`方法,具體請看我整理的: [Web前端開發(fā)規(guī)范文檔](http://kang.cool/modules/web_develop_standard/index.html%20)中的 `javaScript書寫規(guī)范`倒數(shù)第三條目。 * 不過也有另一種說法,通常認(rèn)為需要用 `Array.join`的方式,但是由于 `SpiderMonkey`等引擎對字符串的“ `+`”運(yùn)算做了優(yōu)化,結(jié)果使用`Array.join`的效率反而不如直接用“ `+`”,但是如果考慮 `IE6`,則其他瀏覽器上的這種效率的差別根本不值一提。具體怎么取舍,諸君自定。
作用域鏈和閉包優(yōu)化
- 作用域。
* 作用域( `scope`)是 `JAVASCRIPT`編程中一個(gè)重要的 `運(yùn)行機(jī)制`,在 `JAVASCRIPT`同步和異步編程以及 `JAVASCRIPT`內(nèi)存管理中起著至關(guān)重要的作用。 * 在 `JAVASCRIPT`中,能形成作用域的有如下幾點(diǎn)。 * 函數(shù)的調(diào)用 * with語句 * `with`會創(chuàng)建自已的作用域,因此會增加其中執(zhí)行代碼的作用域的長度。 * 全局作用域。 <pre> `以下代碼為例: var foo = function() { var local = {}; }; foo(); console.log(local); //=undefined var bar = function() { local = {}; }; bar(); console.log(local); //={} /**這里我們定義了foo()函數(shù)和bar()函數(shù),他們的意圖都是為了定義一個(gè)名為local的變量。在foo()函數(shù)中,我們使用var語句來聲明定義了一個(gè)local變量,而因?yàn)楹瘮?shù)體內(nèi)部會形成一個(gè)作用域,所以這個(gè)變量便被定義到該作用域中。而且foo()函數(shù)體內(nèi)并沒有做任何作用域延伸的處理,所以在該函數(shù)執(zhí)行完畢后,這個(gè)local變量也隨之被銷毀。而在外層作用域中則無法訪問到該變量。而在bar()函數(shù)內(nèi),local變量并沒有使用var語句進(jìn)行聲明,取而代之的是直接把local作為全局變量來定義。故外層作用域可以訪問到這個(gè)變量。**/` </pre>
- 作用域鏈
* 在 `JAVASCRIPT`編程中,會遇到多層函數(shù)嵌套的場景,這就是典型的作用域鏈的表示。 <pre> `function foo() { var val = 'hello'; function bar() { function baz() { global.val = 'world;' }; baz(); console.log(val); //=hello }; bar(); }; foo();` </pre>
- 減少作用域鏈上的查找次數(shù)
* `JAVASCRIPT`代碼在執(zhí)行的時(shí)候,如果需要訪問一個(gè)變量或者一個(gè)函數(shù)的時(shí)候,它需要遍歷當(dāng)前執(zhí)行環(huán)境的作用域鏈,而遍歷是從這個(gè)作用域鏈的前端一級一級的向后遍歷,直到全局執(zhí)行環(huán)境。
- 閉包
* `JAVASCRIPT`中的標(biāo)識符查找遵循從內(nèi)到外的原則。 <pre> ` function foo() { var local = 'Hello'; return function() { return local; }; } var bar = foo(); console.log(bar()); //=Hello /**這里所展示的讓外層作用域訪問內(nèi)層作用域的技術(shù)便是閉包(Closure)。得益于高階函數(shù)的應(yīng)用,使foo()函數(shù)的作用域得到`延伸`。foo()函數(shù)返回了一個(gè)匿名函數(shù),該函數(shù)存在于foo()函數(shù)的作用域內(nèi),所以可以訪問到foo()函數(shù)作用域內(nèi)的local變量,并保存其引用。而因這個(gè)函數(shù)直接返回了local變量,所以在外層作用域中便可直接執(zhí)行bar()函數(shù)以獲得local變量。**/`</pre> * 閉包是 `JAVASCRIPT`的高級特性,因?yàn)榘褞в??內(nèi)部變量引用的函數(shù)帶出了函數(shù)外部,所以該作用域內(nèi)的變量在函數(shù)執(zhí)行完畢后的并不一定會被銷毀,直到內(nèi)部變量的引用被全部解除。所以閉包的應(yīng)用很容易造成內(nèi)存無法釋放的情況。 * 良好的閉包管理。 * 循環(huán)事件綁定、私有屬性、含參回調(diào)等一定要使用閉包時(shí),并謹(jǐn)慎對待其中的細(xì)節(jié)。 * 循環(huán)綁定事件,我們假設(shè)一個(gè)場景:有六個(gè)按鈕,分別對應(yīng)六種事件,當(dāng)用戶點(diǎn)擊按鈕時(shí),在指定的地方輸出相應(yīng)的事件。
- 避開閉包陷阱
* 閉包是個(gè)強(qiáng)大的工具,但同時(shí)也是性能問題的主要誘因之一。不合理的使用閉包會導(dǎo)致內(nèi)存泄漏。 * 閉包的性能不如使用內(nèi)部方法,更不如重用外部方法。 * 由于 `IE 9`瀏覽器的 `DOM`節(jié)點(diǎn)作為 `COM`對象來實(shí)現(xiàn), `COM`的`內(nèi)存管理`是通過引用計(jì)數(shù)的方式,引用計(jì)數(shù)有個(gè)難題就是循環(huán)引用,一旦 `DOM`引用了閉包(例如 `event handler`),閉包的上層元素又引用了這個(gè) `DOM`,就會造成循環(huán)引用從而導(dǎo)致內(nèi)存泄漏。
- 善用函數(shù)
* 使用一個(gè)匿名函數(shù)在代碼的最外層進(jìn)行包裹。 <pre> `;(function() { // 主業(yè)務(wù)代碼 })();` </pre>
有的甚至更高級一點(diǎn):
<pre>
;(function(win, doc, $, undefined) { // 主業(yè)務(wù)代碼 })(window, document, jQuery);
</pre>甚至連如RequireJS, SeaJS, OzJS 等前端模塊化加載解決方案,都是采用類似的形式:
<pre>
/**RequireJS**/ define(['jquery'], function($) { // 主業(yè)務(wù)代碼 }); /**SeaJS**/ define('m??odule', ['dep', 'underscore'], function($, _) { // 主業(yè)務(wù)代碼 });
</pre>被定義在全局作用域的對象,可能是會一直存活到進(jìn)程退出的,如果是一個(gè)很大的對象,那就麻煩了。比如有的人喜歡在JavaScript中做模版渲染:
<pre>
<?php $db = mysqli_connect(server, user, password, 'myapp'); $topics = mysqli_query($db, "SELECT * FROM topics;"); ?> <!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>你是猴子請來的逗比么?</title> </head> <body> <ul id="topics"></ul> <script type="text/tmpl" id="topic-tmpl"> <li class="topic"> <h1><%=title%></h1> <p><%=content%></p> </li> </script> <script type="text/javascript"> var data = <?php echo json_encode($topics); ?>; var topicTmpl = document.querySelector('#topic-tmpl').innerHTML; var render = function(tmlp, view) { var complied = tmlp .replace(/\n/g, '\\n') .replace(/<%=([\s\S]+?)%>/g, function(match, code) { return '" + escape(' + code + ') + "'; }); complied = [ 'var res = "";', 'with (view || {}) {', 'res = "' + complied + '";', '}', 'return res;' ].join('\n'); var fn = new Function('view', complied); return fn(view); }; var topics = document.querySelector('#topics'); function init() data.forEach(function(topic) { topics.innerHTML += render(topicTmpl, topic); }); } init(); </script> </body> </html>
</pre>在從數(shù)據(jù)庫中獲取到的數(shù)據(jù)的量是非常大的話,前端完成模板渲染以后,data變量便被閑置在一邊。可因?yàn)檫@個(gè)變量是被定義在全局作用域中的,所以
JAVASCRIPT
引擎不會將其回收銷毀。如此該變量就會一直存在于老生代堆內(nèi)存中,直到頁面被關(guān)閉。可是如果我們作出一些很簡單的修改,在邏輯代碼外包裝一層函數(shù),這樣效果就大不同了。當(dāng)UI渲染完成之后,代碼對data的引用也就隨之解除,而在最外層函數(shù)執(zhí)行完畢時(shí),JAVASCRIPT
引擎就開始對其中的對象進(jìn)行檢查,data也就可以隨之被回收。