我覺得優化javascript是一門高深的學問,在這裡也隻能站在前人的肩膀上,說一些我淺顯的認識,更希望的是抛鑽引玉,如有不對,敬請斧正。
首先,要認識到是,優化js的關鍵之處在于,優化它的運作速度,以此為切入點。
javascript的優化原則是:二八原則
滿足考量大多數情況,而遇到極端情況,有能力則兼顧之,學會放棄,适當取舍;
原因是,影響使用者的體驗很重要的因素之一響應時間
0.1s: 使用者覺得很流暢
1.0s: 使用者的操作可能偶爾受到影響,并且使用者已經能感覺到有些不流暢
10s : 對使用者的影響比較嚴重,需要相應的進度提示。使用者也會有一些沮喪 (//我覺得10s太寬泛了,通常而言2s以上就受不了了)
繼續正題,ok,那麼知道了目标是提升響應時間,加快運作速度,那麼具體有哪些可行的方案呢:
管理作用域
操作資料
流控制
Reflow
DOM操作
長時間運作的腳本處理
管理作用域
舉個闆栗:
1
2
3
4
5
6
7
8
9
<code>var</code> <code>foo = 1;</code>
<code>function</code> <code>test(){</code>
<code> </code><code>//對變量foo進行一系列操作</code>
<code>}</code>
<code>function</code> <code>test2(){</code>
<code> </code><code>var</code> <code>foo = 1;</code>
<code> </code><code>//對變量foo進行一系列操作</code>
也就是說,局部變量存在于活動對象中,解析器隻需查找作用域中的單個對象。
在JavaScript中,我們應該盡可能的用局部變量來代替全局變量,這句話所有人都知道,可是這句話是誰先說的?為什麼要這麼做?有什麼根據麼?不這麼做,對性能到底能帶來多大的損失?以下是我摘自《JavaScript Variable Performance》的一段:
在如何提高JavaScript性能這個問題上,大家最常聽到的建議應該就是盡量使用局部變量(local variables)來代替全局變量(global variables)。在我從事Web開發工作的九年時間裡,這條建議始終萦繞在我的耳邊,并且從來沒有質疑過,而這條建議的基礎,則來自于 JavaScript處理作用域(scoping)和辨別符解析(identifier resolution)的方法。 首先我們要明确,函數在JavaScript中具體表現為對象,建立一個函數的過程,其實也就是建立一個對象的過程。每個函數對象都有一個叫做 [[Scope]]的内部屬性,這個内部屬性包含建立函數時的作用域資訊。實際上,[[Scope]]屬性對應的是一個對象(Variable Objects)清單,清單中的對象是可以從函數内部通路的。比如說我們建立一個全局函數A,那麼A的[[Scope]]内部屬性中隻包含一個全局對象(Global Object),而如果我們在A中建立一個新的函數B,那麼B的[[Scope]]屬性中就包含兩個對象,函數A的Activation Object對象在前面,全局對象(Global Object)排在後面。當一個函數被執行的時候,會自動建立一個可以執行的對象(Execution Object),并同時綁定一個作用域鍊(Scope Chain)。作用域鍊會通過下面兩個步驟來建立,用于進行辨別符解析。 首先将函數對象[[Scope]]内部屬性中的對象,按順序複制到作用域鍊中。 其次,在函數執行時,會建立一個新的Activation Object對象,這個對象中包含了this、參數(arguments)、局部變量(包括命名的參數)的定義,這個Activation Object對象會被置于作用域鍊的最前面。 在執行JavaScript代碼的過程中,當遇到一個辨別符,就會根據辨別符的名稱,在執行上下文(Execution Context)的作用域鍊中進行搜尋。從作用域鍊的第一個對象(該函數的Activation Object對象)開始,如果沒有找到,就搜尋作用域鍊中的下一個對象,如此往複,直到找到了辨別符的定義。如果在搜尋完作用域中的最後一個對象,也就是全局對象(Global Object)以後也沒有找到,則會抛出一個錯誤,提示使用者該變量未定義(undefined)。這是在ECMA-262标準中描述的函數執行模型和辨別符解析(Identifier Resolution)的過程,事實證明,大部分的JavaScript引擎确實也是這樣實作的。需要注意的是,ECMA-262并沒有強制要求采用這種結構,隻是對這部分功能加以描述而已。 了解辨別符解析(Identifier Resolution)的過程以後,我們就能明白為什麼局部變量的解析速度要比其他作用域的變量快,主要是由于搜尋過程被大幅縮短了。
也就是:當辨別符解析的過程需要進行深度搜尋時,會伴随性能損失,而且性能損失的程度會随着辨別符深度的增加而遞增。
資料操作
1. 使用局部變量,它是最快的
obj.name比obj.xxx.name通路更快,通路屬性的速度,與其在對象中的深度有關。“ . ”操作的次數直接影響着通路對象屬性的耗時。
2. 緩存頻繁使用的對象、數組及相關的屬性值
<code>function</code> <code>process(data){</code>
<code> </code><code>var</code> <code>count = data.count;</code>
<code> </code><code>if</code> <code>(count > 0){</code>
<code> </code><code>for</code><code>(</code><code>var</code> <code>i = 0; i < count ; i++){</code>
<code> </code><code>processData(data.item[i]);</code>
<code> </code><code>} </code>
<code> </code><code>}</code>
3. 不直接操作NodeList,将其轉換成靜态數組後再使用
方法: Array.prototype.slice.call() => 标準浏覽器
逐個拷貝到一個新數組中 => For IE
需要注意的是,周遊NodeList時,不做對目前NodeList相關結構有影響的DOM操作,并且如之前所提到的,要緩存一些頻繁使用到的屬性值,以免發生不必要的悲劇。闆栗:
10
<code>var</code> <code>divs = document.getElementsByTagName(</code><code>'DIV'</code><code>);</code>
<code>//假定頁面中有div,是以divs.length是大于0的</code>
<code>for</code> <code>(</code><code>var</code> <code>idx = 0; idx < divs.length; idx++){</code>
<code> </code><code>document.body.appendChild(</code>
<code> </code><code>//杯具悄然而置</code>
<code> </code><code>document.createElement(</code><code>'DIV'</code><code>)</code>
<code> </code><code>);</code>
<code> </code><code>console.info(divs.length);</code>
上面的代碼最後運作會報錯,原因通過不斷地往document.body下插入div 節點,for循環的終止條件( div.length也随之改變)失效,陷入死循環。也就是說通過getElementsByTagName()擷取得到的是一個Live NodeList的引用,任何對其相關的DOM操作都會立即反應在這個NodeList上面。
Dom操作
1. 增删查改
盡量使用DocumentFragment
處理節點時可以使用cloneNode()複制一份
若要對DOM進行直接修改,請先将其display:none;
2. 指明操作DOM的context
context.getElementsByTagName()
3. 拆分方法,一個方法解決一件事
拆分功能,讓一個方法隻做一件事,通過不斷地調用方法來實作複雜功能,但是,這些簡單方法要避免互相交叉調用。
Be Lazy(使腳本盡可能少地運作,或者不運作。)
短路表達式應用:如 a && b || c
基于事件去寫相應的處理方法
惰性函數
流控制
11
12
13
14
<code>if</code><code>(...){</code>
<code>elseif(...){</code>
<code>else</code><code>{</code>
原則:
在if語句中,将經常會發生的條件,放在靠上的位置
if的條件為連續的區間時,可以使用二分法的方式來拆分
較多離散值的判斷,可以使用switch來替代
使用數組查詢的方式
要注意隐式的類型轉換
小心遞歸
<code>var</code> <code>foo = 0;</code>
<code> </code>
<code> </code><code>if</code><code>(foo == </code><code>false</code><code>){ </code><code>//隐式轉換</code>
<code> </code><code>...</code>
<code> </code><code>}</code>
<code>function</code> <code>recurse(){</code>
<code> </code><code>recurse();</code>
<code>recurse(); </code><code>//又是一個悲劇,會報錯,無限遞歸了</code>
Reflow
何為reflow,即是:在CSS規範中有一個渲染對象的概念,通常用一個盒子(box, rectangle)來表示。mozilla通過一個叫frame的對象對盒子進行操作。frame主要的動作有三個:
構造frame, 以建立對象樹(DOM樹)
reflow, 以确定對象位置,或者是調用mozilla的Layout(這裡是指源碼的實作)
繪制,以便對象能顯示在螢幕上
總的來說,reflow就是載入内容樹(在HTML中就是DOM樹)和建立或更新frame結構的響應的一種過程。
那麼造成reflow的原因有:
操作DOM樹
與布局有關的樣式改變
改變className
視窗大小調整
字休大小
是以若要要提高頁面性能,其實就是避免reflow的開銷,但是造成reflow的原因有時候是為了完成互動效果而不可避免的,是以不能說完全避免,隻能盡最大限度的去減少,這就如我開頭而言的二八原則。
以下是一些簡單的指導方針可以幫助你頁面上的回流(reflow)減到最小。
減少不必要的DOM深度。因為無論你改變DOM節點樹上任何一個層級都會影響節點樹的每個層級——從根結點一直到修改的子節點。不必要的節點深度将導緻執行回流時花費更多的時間。
精簡css,去除沒有用處的css
如果你想讓複雜的表現發生改變,例如動畫效果,那麼請在這個流動線之外實作它。使用position-absolute或position-fixed來實作它。
避免不必要的複雜的css選擇符,尤其是使用子選擇器,或消耗更多的CPU去做選擇器比對。
本文轉自ChokCoco部落格園部落格,原文連結:http://www.cnblogs.com/coco1s/p/3946435.html