天天看點

前端工程優化:javascript的優化小結

我覺得優化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 &gt; 0){</code>

<code>        </code><code>for</code><code>(</code><code>var</code> <code>i = 0; i &lt; 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() =&gt; 标準浏覽器

      逐個拷貝到一個新數組中 =&gt; 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 &lt; 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 &amp;&amp; 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

繼續閱讀