1. 程式人生 > >《高效能JavaScript》讀書筆記

《高效能JavaScript》讀書筆記

1.載入和執行

1.把<script>標籤放在儘可能接近<body>標籤底部的位置

因為瀏覽器在遇到<body>標籤之前,不會渲染頁面的任何部分。

2.減少<script>標籤總數

Yahoo! 為他的“Yahoo! 使用者介面(Yahoo! User Interface,YUI)”庫建立一個“聯合控制代碼”,這是通過他們的“內容投遞網路(Content Delivery Network,CDN)”實現的。任何一個網站可以使用一個“聯合控制代碼”URL指出包含 YUI 檔案包中的哪些檔案。例如,下面的 URL 包含兩個檔案:

http://yui.yahooapis.com/combo?2.7.0/build/yahoo/yahoo-min.js&2.7.0/build/event/event-min.js

此 URL 呼叫 2.7.0 版本的 yahoo-min.js 和 event-min.js 檔案。這些檔案在伺服器上是兩個分離的檔案,但是當伺服器收到此 URL 請求時,兩個檔案將被合併在一起返回給客戶端。通過這種方法,就不再需要兩個<script>標籤(每個標籤載入一個檔案),一個<script>標籤就可以載入他們 。

3.非阻塞指令碼

1.defer

<script type="text/javascript" src="file1.js" defer></script>

一個帶有 defer 屬性的<script>標籤可以放置在文件的任何位置。對應的 JavaScript 檔案將在<script>被解析時啟動下載,但程式碼不會被執行,直到 DOM 載入完成(在 onload 事件控制代碼被呼叫之前)。當一個 defer的 JavaScript 檔案被下載時,它不會阻塞瀏覽器的其他處理過程,所以這些檔案可以與頁面的其他資源一起並行下載。 


但是,defer 屬性只被 Internet Explorer 4 和 Firefox 3.5 更高版本的瀏覽器所支援,它不是一個理想的跨瀏覽器解決方案。在其他瀏覽器上,defer 屬性被忽略,<script>標籤按照預設方式被處理(造成阻塞)。  2.動態指令碼元素 

var script = document.createElement ("script");

script.type = "text/javascript";
script.src = "file1.js";document.getElementsByTagName_r("head")[0].appendChild(script); 

新的<script>元素載入 file1.js 原始檔。此檔案當元素新增到頁面之後立刻開始下載。此技術的重點在於:無論在何處啟動下載,檔案的下載和執行都不會阻塞其他頁面處理過程。你甚至可以將這些程式碼放在<head>部分而不會對其餘部分的頁面程式碼造成影響(除了用於下載檔案的 HTTP 連線)。 

3.封裝js動態載入的函式

function loadScript(url, callback){
var script = document.createElement ("script")script.type = "text/javascript";
if (script.readyState){
//IE

script.onreadystatechange = function(){
if (script.readyState == "loaded" || script.readyState == "complete"){

script.onreadystatechange = null;

callback();}

};
} else {
//Others

script.onload = function(){callback();

};}

script.src = url;

document.getElementsByTagName_r("head")[0].appendChild(script);} 


4.推薦的非阻塞模式

<script type="text/javascript" src="loader.js"></script><script type="text/javascript">

loadScript("the-rest.js", function(){Application.init();

});</script> 

或者:

<script type="text/javascript">function loadScript(url, callback){

var script = document.createElement ("script")script.type = "text/javascript";

if (script.readyState){//IEscript.onreadystatechange = function(){

if (script.readyState == "loaded" ||script.readyState == "complete"){

script.onreadystatechange = null;callback();

}};

} else {//Othersscript.onload = function(){

callback();};

}
script.src = url;document.getElementsByTagName_r("head")[0].appendChild(script);

}
loadScript("the-rest.js", function(){

Application.init();});

</script> 

2.資料訪問

1.作用域鏈

函式中區域性變數的訪問速度總是最快的,而全域性變數通常是最慢的 

全域性變數總是處於執行期上下文作用域鏈的最後一個位置,所以總是最遠才能觸及的。 

一般來說,一個執行期上下文的作用域鏈不會被改變。但是,有兩種表示式可以在執行時臨時改變執行期上下文作用域鏈。第一個是 with 表示式。 

function initUI(){
with (document){
//avoid!

var bd = body,
links = getElementsByTagName_r("a"),i = 0,
len = links.length;
while(i < len){

update(links[i++]);}

getElementById("go-btn").onclick = function(){start();

};

bd.className = "active";}

當代碼流執行到一個 with 表示式時,執行期上下文的作用域鏈被臨時改變了。一個新的可變物件將被建立,它包含指定物件的所有屬性。此物件被插入到作用域鏈的前端,意味著現在函式的所有區域性變數都被推入第二個作用域鏈物件中,所以訪問代價更高了 


通過將 document 物件傳遞給 with 表示式,一個新的可變物件容納了 document 物件的所有屬性,被插入到作用域鏈的前端。這使得訪問 document 的屬性非常快,但是訪問區域性變數的速度卻變慢了,例如 bd 變數。正因為這個原因,最好不要使用 with 表示式。正如前面提到的,只要簡單地將 document 儲存在一個區域性變數中,就可以獲得性能上的提升( var doc = document)。 

在 JavaScript 中不只是 with 表示式人為地改變執行期上下文的作用域鏈,try-catch 表示式的 catch 子句具有相同效果。當 try 塊發生錯誤時,程式流程自動轉入 catch 塊,並將異常物件推入作用域鏈前端的一個可變物件中。在 catch 塊中,函式的所有區域性變數現在被放在第二個作用域鏈物件中。例如:

try {methodThatMightCauseAnError();

} catch (ex){
alert(ex.message);
//scope chain is augmented here

只要 catch 子句執行完畢,作用域鏈就會返回到原來的狀態。 

一個很好的模式是將錯誤交給一個專用函式來處理。例子如下:

try {methodThatMightCauseAnError();

} catch (ex){
handleError(ex);
//delegate to handler method

handleError()函式是 catch 子句中執行的唯一程式碼。此函式以適當方法自由地處理錯誤,並接收由錯誤產生的異常物件。由於只有一條語句,沒有區域性變數訪問,作用域鏈臨時改變就不會影響程式碼的效能。 

原型鏈


如果在同一個函式中你要多次讀取同一個物件屬性,最好將它存入一個區域性變數。以區域性變數替代屬性,避免多餘的屬性查詢帶來效能開銷。在處理巢狀物件成員時這點特別重要,它們會對執行速度產生難以置信的影響。 


總結:

區域性變數比域外變數快,因為它位於作用域鏈的第一個物件中。變數在作用域鏈中的位置越深,訪問所需
的時間就越長。全域性變數總是最慢的,因為它們總是位於作用域鏈的最後一環。

避免使用 with 表示式,因為它改變了執行期上下文的作用域鏈。而且應當小心對待 try-catch 表示式的 catch子句,因為它具有同樣效果。 

巢狀物件成員會造成重大效能影響,儘量少用。
一個屬性或方法在原形鏈中的位置越深,訪問它的速度就越慢。

一般來說,你可以通過這種方法提高 JavaScript 程式碼的效能:將經常使用的物件成員,陣列項,和域外變數存入區域性變數中。然後,訪問區域性變數的速度會快於那些原始變數。 

3.DOM程式設計

當瀏覽器下載完所有頁面 HTML 標記,JavaScript,CSS,圖片之後,它解析檔案並建立兩個內部資料結構: 一棵DOM樹表示頁面結構,一棵渲染樹表示 DOM 節點如何顯示。

渲染樹中為每個需要顯示的 DOM 樹節點存放至少一個節點(隱藏 DOM 元素在渲染樹中沒有對應節點)。渲染樹上的節點稱為“框”或者“盒”,符合 CSS 模型的定義,將頁面元素看作一個具有填充、邊距、邊框和位置的盒。一旦 DOM 樹和渲染樹構造完畢,瀏覽器就可以顯示(繪製)頁面上的元素了。

當 DOM 改變影響到元素的幾何屬性(寬和高)——例如改變了邊框寬度或在段落中新增文字,將發生一系列後續動作——瀏覽器需要重新計算元素的幾何屬性,而且其他元素的幾何屬性和位置也會因此改變受到影響。瀏覽器使渲染樹上受到影響的部分失效,然後重構渲染樹。這個過程被稱作重排版。重排版完成時,瀏覽器在一個重繪程序中重新繪製螢幕上受影響的部分。不是所有的 DOM 改變都會影響幾何屬性。例如,改變一個元素的背景顏色不會影響它的寬度或高度。在這種情況下,只需要重繪(不需要重排版),因為元素的佈局沒有改變。 

總結:

DOM 訪問和操作是現代網頁應用中很重要的一部分。但每次你通過橋樑從 ECMAScript 島到達 DOM 島時,都會被收取“過橋費”。為減少 DOM 程式設計中的效能損失,請牢記以下幾點:

小心地處理 HTML 集合,因為他們表現出“存在性”,總是對底層文件重新查詢。將集合的 length 屬性快取到一個變數中,在迭代中使用這個變數。如果經常操作這個集合,可以將集合拷貝到陣列中。

注意重繪和重排版;批量修改風格,離線操作 DOM 樹,快取並減少對佈局資訊的訪問。

動畫中使用絕對座標,使用拖放代理。

使用事件託管技術最小化事件控制代碼數量。

4.迴圈

新知識點: 達夫裝置(用於優化迴圈效能) 

總結:

for,while,do-while 迴圈的效能特性相似,誰也不比誰更快或更慢。

除非你要迭代遍歷一個屬性未知的物件,否則不要使用 for-in 迴圈。

 改善迴圈效能的最好辦法是減少每次迭代中的運算量,並減少迴圈迭代次數。

一般來說,switch 總是比 if-else 更快,但並不總是最好的解決方法。

當判斷條件較多時,查表法比 if-else 或者 switch 更快。

瀏覽器的呼叫棧尺寸限制了遞迴演算法在 JavaScript 中的應用;棧溢位錯誤導致其他程式碼也不能正常執行。

 如果你遇到一個棧溢位錯誤,將方法修改為一個迭代演算法或者使用製表法可以避免重複工作。
 執行的程式碼總量越大,使用這些策略所帶來的效能提升就越明顯

5.字串和正則表示式 

關於正則表示式,之後要進行更深入的學習

1.字串連線

當連線數量巨大或尺寸巨大的字串時,陣列聯合是 IE7 和它的早期版本上唯一具有合理性能的方法。如果你不關心 IE7 和它的早期版本,陣列聯合是連線字串最慢的方法之一。使用簡單的+和+=取而代之,可避免(產生)不必要的中間字串。

2.正則表示式工作原理

正則表示式處理的基本步驟:

Step 1: Compilation

第一步:編譯 

當你建立了一個正則表示式物件之後(使用一個正則表示式直接量或者 RegExp 構造器),瀏覽器檢查你的模板有沒有錯誤,然後將它轉換成一個本機程式碼例程,用於執行匹配工作。如果你將正則表示式賦給一個變數,你可以避免重複執行此步驟。 

第二步:設定起始位置 

當一個正則表示式投入使用時,首先要確定目標字串中開始搜尋的位置。它是字串的起始位置,或者由正則表示式的 lastIndex 屬性指定

第三步:匹配每個正則表示式的字元
 正則表示式一旦找好起始位置,它將一個一個地掃描目標文字和正則表示式模板。當一個特定字元匹配
失敗時,正則表示式將試圖回溯到掃描之前的位置上,然後進入正則表示式其他可能的路徑上。

第四步:匹配成功或失敗 

如果在字串的當前位置上發現一個完全匹配,那麼正則表示式宣佈成功。如果正則表示式的所有可能路徑都嘗試過了,但是沒有成功地匹配,那麼正則表示式引擎回到第二步,從字串的下一個字元重新嘗試。只有字串中的每個字元(以及最後一個字元後面的位置)都經歷了這樣的過程之後,還沒有成功匹配,那麼正則表示式就宣佈徹底失敗。 

提高正則表示式效率 

1.關注如何讓匹配更快失敗 

正則表示式處理慢往往是因為匹配失敗過程慢,而不是匹配成功過程慢。如果你使用正則表示式匹配一個很大字串的一小部分,情況更為嚴重,正則表示式匹配失敗的位置比匹配成功的位置要多得多。如果一個修改使正則表示式匹配更快但失敗更慢(例如,通過增加所需的回溯次數去嘗試所有分支的排列組合),這通常是一個失敗的修改。 

2.正則表示式以簡單的,必需的字元開始 3.減少分支的數量,縮小它們的範圍 4.將正則表示式賦給變數,以重用它們 5.將複雜的正則表示式拆分為簡單的片斷

什麼時候不應該使用正則表示式 

只是搜尋文字字串時

6. 響應介面

新知識點:Web Workers 

網頁工人執行緒適合於那些純資料的,或者與瀏覽器 UI 沒關係的長執行指令碼。它看起來用處不大,而網頁應用程式中通常有一些資料處理功能將受益於工人執行緒,而不是定時器。 

考慮這樣一個例子,解析一個很大的 JSON 字串(JSON 解析將在後面第七章討論)。假設資料足夠大,至少需要 500 毫秒才能完成解析任務。很顯然時間太長了以至於不能允許 JavaScript 在客戶端上執行它,因為它會干擾使用者體驗。此任務難以分解成用於定時器的小段任務,所以工人執行緒成為理想的解決方案。下面的程式碼說明了它在網頁上的應用:

var worker = new Worker("jsonparser.js");

//when the data is available, this event handler is called

worker.onmessage = function(event){

//the JSON structure is passed back

var jsonData = event.data;

//the JSON structure is used

evaluateData(jsonData); 

};

//pass in the large JSON string to parse

worker.postMessage(jsonText);

工人執行緒的程式碼負責 JSON 解析,如下:

//inside of jsonparser.js
//this event handler is called when JSON data is available

self.onmessage = function(event){

//the JSON string comes in as event.data

var jsonText = event.data;

//parse the structure

var jsonData = JSON.parse(jsonText);

//send back to the results

self.postMessage(jsonData);}; 

請注意,即使 JSON.parse()可能需要 500 毫秒或更多時間,也沒有必要新增更多程式碼來分解處理過程。此處理過程發生在一個獨立的執行緒中,所以你可以讓它一直執行完解析過程而不會干擾使用者體驗。 


總結:

JavaScript 和使用者介面更新在同一個程序內執行,同一時刻只有其中一個可以執行。這意味著當 JavaScript程式碼正在執行時,使用者介面不能響應輸入,反之亦然。有效地管理 UI 執行緒就是要確保 JavaScript 不能執行太長時間,以免影響使用者體驗。最後,請牢記如下幾點: 

JavaScript 執行時間不應該超過 100 毫秒。過長的執行時間導致 UI 更新出現可察覺的延遲,從而對整體使用者體驗產生負面影響。 

JavaScript 執行期間,瀏覽器響應使用者互動的行為存在差異。無論如何,JavaScript 長時間執行將導致使用者體驗混亂和脫節。 

 定時器可用於安排程式碼推遲執行,它使得你可以將長執行指令碼分解成一系列較小的任務。

網頁工人執行緒是新式瀏覽器才支援的特性,它允許你在 UI 執行緒之外執行 JavaScript 程式碼而避免鎖定 UI。 

網頁應用程式越複雜,積極主動地管理 UI 執行緒就越顯得重要。沒有什麼 JavaScript 程式碼可以重要到允

許影響使用者體驗的程度。 


7.Ajax

注意 跨域、jsonp、 動態標籤


8.程式設計方面的優化

通過避免使用 eval_r()和 Function()構造器避免二次評估。此外,給 setTimeout()和 setInterval()傳遞函式引數而不是字串引數。

 建立新物件和陣列時使用物件直接量和陣列直接量。它們比非直接量形式建立和初始化更快。
 避免重複進行相同工作。當需要檢測瀏覽器時,使用延遲載入或條件預載入。
 當執行數學遠算時,考慮使用位操作,它直接在數字底層進行操作。

原生方法總是比 JavaScript 寫的東西要快。儘量使用原生方法。


9.開發和部署

開發和部署過程對基於 JavaScript 的應用程式可以產生巨大影響,最重要的幾個步驟如下:

合併 JavaScript 檔案,減少 HTTP 請求的數量

使用 YUI 壓縮器緊湊處理 JavaScript 檔案

以壓縮形式提供 JavaScript 檔案(gzip 編碼)

通過設定 HTTP 響應報文頭使 JavaScript 檔案可快取,通過向檔名附加時間戳解決快取問題

使用內容傳遞網路(CDN)提供 JavaScript 檔案,CDN 不僅可以提高效能,它還可以為你管理壓縮和快取、

所有這些步驟應當自動完成,不論是使用公開的開發工具諸如 Apache Ant,還是使用自定義的開發工具以實現特定需求。如果你使這些開發工具為你服務,你可以極大改善那些大量使用 JavaScript 程式碼的網頁應用或網站的效能。