1. 程式人生 > >讀書筆記 — 《高效能JavaScript》4-8章

讀書筆記 — 《高效能JavaScript》4-8章

第4章 演算法和流程控制

內容:

程式演算法和流程優化,包括優化迴圈、遞迴等等

知識點:

  1. js有四種迴圈型別:
for(var i=0; i<10; i++) {};   //初始化、前測條件、後執行體、迴圈體
while() {}   //前側條件、迴圈體
do{} while();   //迴圈體、後測條件
for(var prop in object) {}

for-in迴圈所返回的物件包括例項屬性和從原型鏈中繼承的屬性,它是四種迴圈中最慢的,大約只有其他型別速度的1/7;
2. 優化迴圈有兩種方式:
減少迭代工作量 ->複雜度=0(n)時首選
減少迭代次數 ->複雜度>0(n)時首選
3. 減少迭代工作量方法:
在初始化中將length儲存為區域性變數,例如:for(let i=0, len=arr.length; i<len; i++)


使用倒序迴圈,例如:for(let i=10;i--;){}while(j--){},每次迴圈減少一次控制條件判斷(迭代次數少於上限?值是否為true?=>值是否為true?);
4. 減少迭代次數方法:
“達夫裝置”,每次迴圈執行多次操作,在迭代次數>1000時考慮使用;
5. 大多數情況switch比if-else更快,但只要當條件數量很大時才慢得明顯。因為大多數語言對switch採用分支表索引進行優化,並且在js中在switch中使用全等操作符,不會發生型別轉換;
6. 條件語句選擇:
離散值 => switch
多個值域 => if-else
大量離散值且條件和值存在對映關係 => 查詢表(存入陣列直接return)
7. 優化if-else方法:最可能出現的條件放在首位,巢狀if-else(二分法);
8. 除IE的呼叫棧是與系統空閒記憶體有關,其他瀏覽器都有固定數量的呼叫棧限制;

瀏覽器 呼叫棧錯誤提示 錯誤型別
IE Stack overflow at line x 常規Error
Firefox Too much recursion InternalError
Safari Maximum call stack size exceeded RangeError
Chrome 不提示 RangeError
Opera Abort(control stack overflow) 終止js引擎,不丟擲錯誤

9. 兩種遞迴模式:直接遞迴模式、隱伏模式;

總結:

  1. 避免使用for-in迴圈,除非需要遍歷一個屬性數量未知的物件;
  2. 改善迴圈的最佳方式是減少迭代工作量和減少迭代次數;
  3. 在判斷條件較多時,使用查詢表比if-else和switch更快;
  4. 使用遞迴時注意瀏覽器呼叫棧限制;
  5. 遇到棧溢位錯誤時可將方法改為迭代演算法,或用Memoization避免重複計算。

第5章 字串和正則表示式

內容:

字串操作優化和正則表示式查詢優化,包括優化字串連線、正則匹配等等

知識點:

  1. 例如str+='one' + 'two',js會建立一個臨時字串儲存onetwo的連線然後與str連線再賦值給str,例如使用str=str + 'one' + 'two'可以避免建立臨時字串,因為會從左至右依次連線,但是改變連線順序例如str='one' + str + 'two'則優化失效;
  2. IE7及以前版本的字串連線使用了非常糟糕的方法:每連線一對字串都把它複製到一塊新分配的記憶體中;IE8的實現則是:記錄現有字串的引用來構造新字串,當需要使用時才將各部分逐個拷貝到一個新的字串中,然後用它取代之前的字串引用;
  3. Array.prototype.join方法在大多瀏覽器中比常規字串連線更慢,但在IE7及更早版本中是一種優化手段;
  4. 正則優化部分待續

第6章 快速響應的使用者介面

內容:

使用各種技術提升應用互動性,包括合理管理UI執行緒、使用Web Worker進行額外計算等等

知識點:

  1. 用於執行js和更新UI的程序被稱為“瀏覽器UI執行緒”,它的工作基於一個簡單的佇列系統,任務會被儲存到佇列中直到程序空閒;
  2. 任務型別包括:執行js程式碼、執行UI更新;js執行期間不會更新UI,UI更新期間不會執行js;js與UI共享同一程序的部分原因是它們之間會頻繁訪問;
  3. 瀏覽器對指令碼執行的限制有:呼叫棧大小限制、執行時間限制,各瀏覽器的限制如下:
瀏覽器 限制類型 修改方法
IE 500萬條語句 修改登錄檔MaxScriptStatements欄位
Firefox 10秒 修改瀏覽器配置,輸入about:conf修改dom.max_script_run_time
Safari 5秒 無法修改,可以禁用定時器
Chrome 依賴通用崩潰檢測系統處理
Opera 無限制

4. 單個js操作花費總時間不應該超過100ms;
5. 有些瀏覽器在js執行期間不會把UI更新任務加入佇列,這是為了保證UI頁面的動態變化,所以可能出現指令碼執行期間點選按鈕,無法看到它被按下的樣式,但它的onlick事件處理器會被執行;
6. 使用定時器管理UI執行緒:
- 定時器從呼叫它時開始算,計時完成後被加入UI佇列,但需要注意函式只有在建立它的函式執行完成之後才有可能被執行
- 定時器會重置所有相關的瀏覽器限制,包括長時間執行指令碼定時器,呼叫棧也會重置為0
- js定時器通常不太精確,相差大約幾毫秒。因為例如windows系統定時器解析度是15ms,即一個15ms的定時器會根據最後一次系統時間重新整理而轉換為0或15。最小值最好為25ms(實際時間15或30)以確保至少有15ms的延時,再小的延時對大多數UI更新會不夠用
7. 記錄程式碼執行時間:

var start = +new Date(),
    stop;
process();
stop = +new Date();
var time = stop - start;
  1. 當任務與UI無關且不能被分解時可考慮使用Worker
    Web Worker執行環境:
    • navigator,包含屬性:appName、appVersion、user Agent、platform
    • location,只讀
    • self,指向全域性worker物件
    • importScripts(),用來載入外部js檔案,阻塞式,直到執行完成才繼續
    • 所有ES物件,Object、Array、Date等
    • XMLHttpRequest
    • setTimeout()、setInterval()
    • close(),終止Worker執行
  2. 使用Worker需要單獨建立js檔案然後使用:var worker = new Worker('code.js'),檔案會非同步下載,檔案下載執行完成後才會啟動此Worker;
  3. 主程序端:
var worker = new Worker('code.js');
worker.onmessage = function(e) {
    console.log(e.data);
}
worker.postMessage("s");

Worker端:

self.onmessage = function(e) {
    self.postMessage(e.data);
}

總結:

  1. 尋求執行速度和使用者體驗的平衡,當js任務執行超過100ms時,就應該考慮使用定時器或者Web Worker;
  2. 可以將任務分解成一個一個的小塊分別執行;

第7章 Ajax

內容:

使用各種技術提升應用互動性,包括合理管理UI執行緒、使用Web Worker進行額外

知識點:

1 向伺服器請求資料的常用技術:
- XHR
- 動態指令碼注入
- Multipart XHR
- iframes
- Comet
2 GET請求具有冪等性,對伺服器無副作用,經GET請求的資料會被快取起來;
3 IE限制URL長度,當URL長度超過2048個字元(4k)時應使用POST;
4 可以使用“流”分段處理返回資料,低版本IE不支援“流”,也不提供readyState為3的狀態;
5 使用動態指令碼注入有很多限制,包括不能設定請求頭,只能用GET,不能設定請求超時或重試,必須等所有資料返回才能訪問等等,實現JSONP(JSON with padding)跨域:

//js端
var scriptEle = document.createElement('script');
scriptEle.src = "url";
document.getElementsByTagName('head')[0].appendChild(scriptEle);

function callBack(jsonString) {
    var data = eval('(' + jsonString + ')');
}

//服務端返回
callBack({"status":1, "data": 2});

6 可以將引數以var params = ['key1=value1','key2=value2']格式存為陣列,最後params .join(‘&’)拼接;
7 GET只發送一個數據包,而POST至少傳送兩個(請求頭、請求正文),所以GET請求速度會更快;
8 向伺服器傳送資料技術:XHR、Beacons(信標)
9 Beacons例項:(new Image()).src = url + '?' + params .join('&');
是給伺服器回傳資訊最有效的方式,效能消耗小且服務端錯誤不會影響客戶端;
接收伺服器返回資料:監聽Image物件的load事件;檢查伺服器返回圖片的寬高(如可以用寬1表示成功,2表示重試);
10 XML優點:
- 極佳的通用性,服務端和客戶端都完美支援
- 格式嚴格
- 易於驗證
但也存在過於冗長,語法模糊的問題,解析它也需要提前知道它的結構
11 XHR獲取的JSON是字串格式,而JSON-P本身就被當做JSON物件;
12 兩種情形避免使用JSON-P,因為JSON-P必須是可執行js,可能被任何人呼叫並使用動態指令碼注入技術插入任何網站;不能把敏感資料編碼在JSON-P中,因為你無法確認它是否保持私有呼叫狀態,即使帶有隨機URL和做了cookie判斷;
13 最快的ajax請求就是沒有請求,減少不必要請求方法:
- 服務端,設定HTTP頭資訊確保響應被瀏覽器快取(設定Expires頭資訊,GMT日期格式)
- 客戶端,把獲取的資訊儲存到本地,避免再次請求
14 大多數瀏覽器支援XMLHttpRequest物件,老版本瀏覽器使用ActiveX物件,並需要傳入版本號,通用獲取xhr物件方法:

function createXhrObject(){
    var msxml_progid = [
        'MSXML2.XMLHTTP.6.0',
        'MSXML3.XMLHTTP',
        'Microsoft.XMLHTTP',   //不支援readyState 3
        'MSXML2.XMLHTTP.3.0',   //不支援readyState 3
    ];
    var req;
    try{
        req = new XMLHttpRequest();
    }
    catch(e){
        for(var i = 0, len = msxml_progid.length; i<len; i++){
            try{
                req = new ActiveXObject(msxml_progid[i]);
                break;
            }
            catch(e2){}
        }
    }
    finally{
        return req;
    }
}

15 各種互動手段比較:
- XML支援良好,但笨重且解析緩慢;JSON輕量級解析快,但需要編寫額外的服務端構造程式;
- 動態指令碼注入允許跨域請求和本地執行js和JSON,但它的介面不安全,而且不能讀取頭資訊或響應程式碼;
- Multipart XMR可以減少請求數並處理一個響應中的各種檔案型別,但不能快取接收到的響應

總結:

  1. 減少請求數,合併js和css檔案或使用MXHR;
  2. 頁面主要內容載入完成後用ajax獲取次要檔案;
  3. 確認程式碼錯誤不會輸出給使用者,並在服務端處理錯誤;
  4. 可以使用ajax類庫,但必要時可編寫自己的底層ajax程式碼。

第8章 程式設計實踐

內容:

一些程式設計中需要注意的優化細節,包括條件預載入、使用底層方法等等。

知識點:

1 傳入字串並動態執行的方法:eval()、Function()建構函式、setTimeout()、setInterval();
2 每次呼叫eval()都要建立一個新的直譯器例項;
3 避免重複的條件判斷:延遲載入、條件預載入
當一個函式在頁面中不會立刻呼叫時,延遲載入是最好的選擇:

function addHandler(target, eventType, handler){
    if(target.addEventListener){
        //複寫函式
        addHandler = function(target, eventType, handler){
            target.addEventListener(eventType, handler, false);
        }
    }else{
        addHandler = function(target, eventType, handler){
            target.attachEvent("on" + eventType, handler);  
        }
    }
    //呼叫新函式
    addHandler(target, eventType, handler);
}

條件預載入:函式定義時判定

var addHandler = document.body.addEventListener?
    function(target, eventType, handler){
        target.addEventListener(eventType, handler, false);
    }:
    function(target, eventType, handler){
        target.attachEvent("on" + eventType, handler);  
    }
}

4 js引擎是由低階語言構建的而且經過編譯,使用位操作符和原生方法例如Math中的方法和querySelectorAll()速度更快;
5 位操作符應用:i&1可以判斷奇偶,奇時返回true,偶時返回false

總結:

  1. 避免使用給eval()、Function()構造器、setTimeout()、setInterval()傳入字串來造成雙重求值,因為建立新的直譯器會帶來效能損耗,setTimeout()和setInterval()應傳遞函式;
  2. 儘量使用直接量建立物件和陣列,直接量的建立和初始化比非直接量形式快;
  3. 使用延遲載入和條件預載入避免重複的檢測工作;
  4. 進行數學運算時考慮使用直接運算元字的二進位制形式的位運算,儘量使用原生方法。