高級之路篇三:高性能js

腳本

 

1、將所有<script>標籤放在儘可能接近<body>標籤底部的位置,儘量減少對整個頁面下載的影響;

2、減少引用外部腳本文件的數量;

3、將腳本成組打包。頁面的 <script>標籤越少,頁面的加載速度就越快,響應也更加迅速。不論外部腳本文件還是內聯代碼都是如此。

 

函數作用域

 

4、JavaScript 中,數據存儲位置可以對代碼整體性能產生重要影響。有四種數據訪問類型:直接量,變量,數組項,對象成員。它們有不同的性能考慮。直接量和局部變量的訪問速度要快於數組項和對象成員的訪問速度。

5、某個函數中的作用域鏈的搜索也會消耗性能,在運行期上下文的作用域鏈中,一個標識符所處的位置越深,它的讀寫速度就越慢。所以,函數中局部變量的訪問速度總是最快的,而全局變量通常是最慢的(優化的 JavaScript 引擎在某些情況下可以改變這種狀況)。請記住,全局變量總是處於運行期上下文作用域鏈的最後一個位置,所以總是最遠才能觸及的。最好儘可能使用局部變量。一個好的經驗法則是:用局部變量存儲本地範圍之外的變量值。

6、一個 try-catch 語句不應該作爲JavaScript 錯誤的解決辦法。如果你知道一個錯誤會經常發生,那說明應當修正代碼本身的問題。

7、通常,一個函數的激活對象與運行期上下文一同銷燬。當涉及閉包時,激活對象就無法銷燬了,因爲引用仍然存在於閉包的 [[Scope]]屬性中。這意味着腳本中的閉包與非閉包函數相比,需要更多內存開銷。最好是小心地使用閉包,內存和運行速度都值得被關注。但是,你可以通過本章早先討論過的關於域外變量的處理建議,減輕對運行速度的影響:將常用的域外變量存入局部變量中,然後直接訪問局部變量。

 

prototype原型

 

8、對象可以有兩種類型的成員:實例成員(也稱作 “own”成員)和原形成員。實例成員直接存在於實例自身,而原形成員則從對象原形繼承。你可以使用 hasOwnProperty()函數確定一個對象是否具有特定名稱的實例成員,(它的參數就是成員名稱)。要確定對象是否具有某個名稱的屬性,你可以使用操作符 in

9、深入原形鏈越深,搜索的速度就會越慢。記住,搜索實例成員的過程比訪問直接量或者局部變量負擔更重,所以增加遍歷原形鏈的開銷正好放大了這種效果。

10、成員嵌套越深,訪問速度越慢。 location.href 總是快於window.location.href ,而後者也要window.location.href.toString()更快。如果這些屬性不是對象的實例屬性,那麼成員解析還要在每個點上搜索原形鏈,這將需要更長時間。

11、由於所有這些性能問題與對象成員有關,所以如果可能的話請避免使用它們。更確切地說,你應當小心地,只在必要情況下使用對象成員。例如,沒有理由在一個函數中多次讀取同一個對象成員的值。一般來說,如果在同一個函數中你要多次讀取同一個對象屬性,最好將它存入一個局部變量。以局部變量替代屬性,避免多餘的屬性查找帶來性能開銷。在處理嵌套對象成員時這點特別重要,它們會對運行速度產生難以置信的影響。

 

DOM編程

 

12、一般經驗法則是:輕輕地觸DOM,並儘量保持在 ECMAScript 範圍內完成操作,一次性加載到DOM。

13、更新頁面時,使用雖不標準卻被良好支持innerHTML 屬性更好呢,還是使用純 DOM 方法,如document.createElement () 更好呢?如果不考慮標準問題,它們的性能如何?答案是:性能差別不大,但是,在所有瀏覽器中, innerHTML 速度更快一些,除了最新的基於 WebKit 的瀏覽器(Chrome Safari)。

14、使用 DOM 方法更新頁面內容的另一個途徑是克隆已有 DOM 元素,而不是創建新的——即使用element.cloneNode()element 是一個已存在的節點)代替 document.createElement();在大多數瀏覽器上,克隆節點更有效率,但提高不太多。

15、當每次迭代過程訪問集合的 length 屬性時,它導致集合器更新,在所有瀏覽器上都會產生明顯的性能損失。對一個相關的小集合進行遍歷,只要將 length 緩存一下就足夠好了。但是遍歷數組比遍歷集合快,如果先將集合元素拷貝到數組,訪問它們的屬性將更快。

function loopCacheLengthCollection() {

     var coll = document.getElementsByTagName_r('div'),

     len = coll.length;

     for (var count = 0; count < len; count++) {

         ...... 

     }

}

 

16、訪問集合元素時使用局部變量,自定義toArray()函數可認爲是一個通用的集合轉數組函數。

 

function toArray(coll) {

     for (var i = 0, a = [], len = coll.length ; i < len ; i++) {

          a[i] = coll[i];

     }

     return a;

}

 

17、IE 中, nextSibling 表現得比childNode 好。IE6 中, nextSibling 比對手快16 倍,而在IE7 中快樂 105 倍。鑑於這些結果,在老的 IE 中性能嚴苛的使用條件下,用 nextSibling 抓取DOM 是首選方法。在其他情況下,主要看個人和團隊偏好。

18、在子節點操作中,IE67 8 只支持 children在所有瀏覽器中 children childNodes 更快,雖然差別不是太大,通常快 1.5 3倍。特別值得注意的是 IE,遍歷children 明顯快於遍歷 childNodes——IE6 中快24 倍,在 IE7 中快124倍。

19、如果瀏覽器支持 document.querySelectorAll(),那麼最好使用它。如果你使用JavaScript 庫所提供的選擇器API,確認一下該庫是否確實使用了原生方法。如果不是,你大概需要將庫升

級到新版本。支持選擇器 APIInternet Explorer 8 Firefox 3.5 Safari 3.1Chrome 1 Opera 10另一個函數 querySelector()獲益,這個便利的函數只返回符合查詢條件的第一個節點。

20、重繪與重排版,DOM 改變影響到元素的幾何屬性(寬和高) ——例如改變了邊框寬度或在段落中添加文字,將發生一系列後續動作 ——瀏覽器需要重新計算元素的幾何屬性,而且其他元素的幾何屬性和位置也會因此改變受到影響。瀏覽器使渲染樹上受到影響的部分失效,然後重構渲染樹。這個過程被稱作重排版。重排版完成時,瀏覽器在一個重繪進程中重新繪製屏幕上受影響的部分。

不是所有的 DOM 改變都會影響幾何屬性。例如,改變一個元素的背景顏色不會影響它的寬度或高度。在這種情況下,只需要重繪(不需要重排版),因爲元素的佈局沒有改變。

重繪和重排版是負擔很重的操作,可能導致網頁應用的用戶界面失去相應。所以,十分有必要儘可能減少這類事情的發生。除了上面說的改變幾何寬高時會重排版外,還有添加或刪除可見的 DOM 元素、元素位置改變、元素尺寸改變(因爲邊距,填充,邊框寬度,寬度,高度等屬性改變)、內容改變,例如,文本改變或圖片被另一個不同尺寸的所替代、最初的頁面渲染、瀏覽器窗口改變尺寸、

21、下面這些方法:

offsetTop, offsetLeft, offsetWidth, offsetHeight

scrollTop, scrollLeft, scrollWidth, scrollHeight

clientTop, clientLeft, clientWidth, clientHeight

getComputedStyle() ( currentStyle in IE)(在 IE 中此函數稱爲currentStyle

佈局信息由這些屬性和方法返回最新的數據,所以瀏覽器不得不運行渲染隊列中待改變的項目並重新排版以返回正確的值。在改變風格的過程中,最好不要使用上面列出的那些屬性。任何一個訪問都將刷新渲染隊列,即使你正在獲取那些最近未發生改變的或者與最新的改變無關的佈局信息。

22、重排版和重繪代價昂貴,所以,提高程序響應速度一個好策略是減少此類操作發生的機會。爲減少發生次數,你應該將多個 DOM 和風格改變合併到一個批次中一次性執行。考慮這個例子:

var el = document.getElementById('mydiv');

el.style.borderLeft = '1px';

el.style.borderRight = '2px';

el.style.padding = '5px';

 

糟糕的例子中,它導致瀏覽器重排版了三次。大多數現代瀏覽器優化了這種情況只進行一次重排版,但是在老式瀏覽器中,或者同時有一個分離的同步進程(例如使用了一個定時器),效率將十分低下。一個達到同樣效果而效率更高的方法是:將所有改變合併在一起執行,只修改DOM 一次。可通過使用cssText 屬性實現:

 

var el = document.getElementById('mydiv');

el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';

 

cssText 屬性,覆蓋已存在的風格信息。如果你打算保持當前的風格,你可以將它附加在cssText 字符串的後面。

 

el.style.cssText += "; border-left: 1px;";  //不推薦頻繁的字符串拼接,借用數組實現,後續講解。

 

另一個一次性改變風格的辦法是修改 CSS 的類名稱,而不是修改內聯風格代碼。這種方法適用於那些風格不依賴於運行邏輯,不需要計算的情況。

 

var el = document.getElementById('mydiv');

el.className = 'active';

 

23、當你需要對 DOM 元素進行多次修改時,你可以通過以下步驟減少重繪和重排版的次數:從文檔流中摘除該元素(推薦方法:臨時隱藏元素,進行修改,然後再顯示它。第二種方法就是用文檔片段,文檔片斷是一個輕量級的 document 對象,它被設計專用於更新、移動節點之類的任務。第三種解決方法首先創建要更新節點的副本(克隆:cloneNode),然後在副本上操作,最後用新節點覆蓋老節點。)、對其應用多重改變、將元素帶回文檔中

24、動畫流使用以下步驟可以避免對大部分頁面進行重排版:使用絕對座標定位頁面動畫的元素,使它位於頁面佈局流之外;啓動元素動畫。當它擴大時,它臨時覆蓋部分頁面。這是一個重繪過程,但隻影響頁面的一小部分,避免重排版並重繪一大塊頁面;當動畫結束時,重新定位,從而只一次下移文檔其他元素的位置

25、自從版本 7 之後,IE 可以在任何元素(嚴格模式)上使用 :hover 這個CSS 僞選擇器。然而,如果大量的元素使用了 :hover 那麼會降低反應速度。此問題在 IE8 中更顯著。

26、連接每個句柄都是有代價的,簡單而優雅的處理 DOM 事件的技術是事件託管。它基於這樣一個事實:事件逐層冒泡總能被父元素捕獲。採用事件託管技術之後,你只需要在一個包裝元素上掛接一個句柄,用於處理子元素髮生的所有事件。根據dom標準,每個事件都有三個階段:捕獲、到達目標、冒泡,雖然IE沒有捕獲階段,但是實現事件託管技術只需要冒泡就足夠了。默認冒泡的頂層是window。

 

算法和流控制

 

27、四種循環方式:for(){.....}屬於直接循環,while(){......}屬於前預測循環、do{....}while()屬於後測試循環、for(var i in ...){.....}屬於枚舉循環。

28、在同樣的循環迭代操作中, for-in 循環比其他類型的循環慢 7 倍之多。因此推薦的做法如下:除非你需要對數目不詳的對象屬性進行操作,否則避免使用 for-in 循環。

29、for-in 循環外,其他循環類型性能相當,難以確定哪種循環更快。選擇循環類型應基於需求而不是性能。JavaScript 中,倒序循環可以略微提高循環性能,只要你消除因此而產生的額外操作。

 

///////第一種

for (var i=items.length; i--; ){

     process(items[i]);

}

 

///////第二種

 

var j = items.length;

while (j--){

     process(items[j]]);

}

 

///////第三種

 

var k = items.length-1;

do {

     process(items[k]);

} while (k--);

 

ECMA第四版新增了一個方法forEach(),三個參數:值、索引、數組本身,速度方面滯後,慎用!

 

items.forEach(function(value, index, array){

     process(value);

});

 

30、使用 if-else 或者switch 的流行理論是基於測試條件的數量:條件數量較大,傾向於使用 switch 而不是if-else。這通常歸結到代碼的易讀性。這種觀點認爲,如果條件較少時, if-else 容易閱讀,而條件較多時 switch更容易閱讀。

31、優化 if-else 的目標總是最小化找到正確分支之前所判斷條件體的數量。最簡單的優化方法是將最常見的條件體放在首位。另外一種減少條件判斷數量的方法是將 if-else 組織成一系列嵌套的if-else 表達式。使用一個單獨的一長串的 if-else 通常導致運行緩慢,因爲每個條件體都要被計算。

32、當有大量離散值需要測試時, if-else switch 都比使用查表法要慢得多。在 JavaScript 中查表法可使用數組或者普通對象實現,查表法訪問數據比 if-else 或者switch 快,特別當條件體的數目很大時。當使用查表法時,必須完全消除所有條件判斷。操作轉換成一個數組項查詢或者一個對象成員查詢。使用查表法的一個主要優點是:由於沒有條件判斷,當候選值數量增加時,很少,甚至沒有增加額外的性能開銷。

33、遞歸函數的問題是,一個錯誤定義,或者缺少終結條件可導致長時間運行,凍結用戶界面。此外,遞歸函數還會遇到瀏覽器調用棧大小的限制。IE例外,IE只與系統內存有關,其它瀏覽器由瀏覽器來決定。當你使用了太多的遞歸,超過最大調用棧尺寸時,瀏覽器會出錯並彈出以下信息:

Internet Explorer: “Stack overflow at line x”

Chrome 是唯一不顯示調用棧溢出錯誤的瀏覽器。

34、任何可以用遞歸實現的算法都可以用迭代實現,循環比反覆調用一個函數的開銷要低。

35、減少工作量就是最好的性能優化技術。代碼所做的事情越少,它的運行速度就越快。根據這些原則,避免重複工作也很有意義。多次執行相同的任務也在浪費時間。製表,通過緩存先前計算結果爲後續計算所重複使用,避免了重複工作。這使得製表成爲遞歸算法中有用的技術。

 

function memfactorial(n){

     if (!memfactorial.cache){

          memfactorial.cache = {

               "0": 1,

               "1": 1

          };

     }

     if (!memfactorial.cache.hasOwnProperty(n)){

          memfactorial.cache[n] = n * memfactorial (n-1);

     }

     return memfactorial.cache[n];

}

 

這個使用製表技術的階乘函數的關鍵是建立一個緩存對象。此對象位於函數內部,並預置了兩個最簡單的階乘: 0 1 。在計算階乘之前,首先檢查緩存中是否已經存在相應的計算結果。沒有對應的緩衝值說明這是第一次進行此數值的計算,計算完成之後結果被存入緩存之中,以備今後使用。

 

字符串和正則表達式

 

36、向字符串末尾不斷地添加內容,來創建一個字符串(例如,創建一個 HTML 表或者一個XML 文檔),但此類處理在一些瀏覽器上表現糟糕而遭人痛恨。很頻繁的拼接字符串建議結合數組的push()、join()方法來實現或更好!

當連接數量巨大或尺寸巨大的字符串時,數組聯合是 IE7 和它的早期版本上唯一具有合理性能的方法。

如果你不關心 IE7 和它的早期版本,數組聯合是連接字符串最慢的方法之一。使用簡單的 ++= 取而代之,可避免(產生)不必要的中間字符串。

37、儘量避免一個正則表達式做太多的工作。複雜的搜索問題需要條件邏輯,拆分爲兩個或多個正則表達式更容易解決,通常也更高效,每個正則表達式只在最後的匹配結果中執行查找。

38、正則以什麼字符結尾的,建議用charAt函數在特定位置上讀取字符,字符串函數slice,substr,substring可用於在特定位置上提取並檢查字符串的值。所有這些字符串操作函數速度都很快,當您搜索那些不依賴正則表達式複雜特性的文本字符串時,它們有助於您避免正則表達式帶來的性能開銷。

 

響應接口

 

39、響應時間儘量控制在100ms內,用戶會覺得自己就是成立裏面的一個對象,超過100ms用戶就會覺得已經和程序斷開。但是我會消減一半,界定爲50ms。

40、JavaScript 定時器延時往往不準確,快慢大約幾毫秒。正因爲這個原因,定時器不可用於測量實際時間。Windows 系統上定時器分辨率爲 15 毫秒,所以最小值建議爲 25 毫秒(實際時間是15 30)以確保至少15 毫秒延遲。

41、通常將一個任務分解成一系列子任務。

 

Aja異步JavaScript和XML

 

42、在現代高性能 JavaScript 中使用的三種技術是XHR(XMLHttpRequest)、動態腳本標籤插入(同事還可以解決跨越問題的黑客技術)、多部分的 XHR。使用Cometiframe(作爲數據傳輸技術)往往是極限情況,不在這裏討論。

 

var url = '/data.php';

var params = [

     'id=934875',

     'limit=20'

];

var req = new XMLHttpRequest();

req.onreadystatechange = function() {

     if (req.readyState === 4) { //4表示整個響應報文已經接收完可用於操作。等於3呢,表示此時正在與服務器交互,報文還在傳輸中,即所謂的“流”,這一步是提高數據請求性能的關鍵所在,但是在IE6/7中是沒有這個狀態值的!!!

          var responseHeaders = req.getAllResponseHeaders(); // Get the response headers.

          var data = req.responseText; // Get the data.

          // Process the data here...

     }

}

req.open('GET', url + '?' + params.join('&'), true);

req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // Set a request header.

req.send(null); // Send the request.

 

43、如果請求不改變服務器狀態只是取回數據(又稱作冪等動作)則使用 GETGET 請求被緩衝起來,如果你多次提取相同的數據可提高性能。只有當 URL 和參數的長度超過了2'048 個字符時才使用 POST 提取數據。因爲Internet Explorer 限制 URL的長度,過長將導致請求(參數)被截斷。

44、最新的技術,多部分 XHRMXHR )允許你只用一個 HTTP 請求就可以從服務器端獲取多個資源。它通過將資源(可以是 CSS 文件,HTML 片段,JavaScript 代碼,或 base64 編碼的圖片)打包成一個由特定分隔符界定的大字符串,從服務器端發送到客戶端。 JavaScript 代碼處理此長字符串,根據它的媒體類型和其他 信息頭 解析出每個資源。

45、在考慮數據傳輸技術時,你必須考慮這些因素:功能集,兼容性,性能,和方向(發給服務器或者從服務器接收)。在考慮數據格式時,唯一需要比較的尺度的就是速度。

46、精簡對象屬性名,split()是最快的字符串操作之一。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章