高性能JavaScript讀書筆記

參考:《高性能JavaScript》

1、加載和執行

1. 不管當前的JavaScript代碼是內嵌仍是包含在外鏈文件中,頁面的下載和渲染都必須停下來等待腳本執行完成

2. 瀏覽器在解析到<body>標籤以前,不會渲染頁面的任何部分(所以將腳本放到body標籤底部,能夠減小對頁面的阻塞時間)

3. 肯定該腳本不會修改DOM元素,可使用defer屬性異步加載腳本

  • defer下載完成後不會立刻執行,直到DOM加載完成,onload事件被觸發前才執行
  • async--HTML5的屬性,也是異步加載腳本,一加載完就執行

4. ==動態腳本==加載憑藉着它在跨瀏覽器兼容性和易用的優點,成爲==最通用的無阻塞加載解決方案==

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 { //其它瀏覽器
        script.onload = function(){
            callback();
        }
    }

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

5. 常見無阻塞腳本加載工具:

  • YUI3
  • LazyLoad類庫
  • LABjs

小結

減小對JavaScript對頁面加載性能影響的幾種辦法:javascript

  • </body>標籤閉合以前,將全部的<script>標籤放到頁面底部。這能確保在腳本執行前已經完成渲染。
  • 合併腳本。頁面的<script>標籤越少,加載也就越快,響應也更迅速。不管外鏈仍是內嵌腳本都是如此。
  • 有多種無阻塞下載JavaScript的方法:php

    • 使用<scipt>標籤的deferasync屬性;
    • 使用動態建立的<scipt>元素來下載並執行代碼;
    • 使用XHR對象下載JavaScript代碼並注入頁面中。

2、數據存取

1. 四種基本的數據存取位置

  • 字面量:css

    • 字面量只表明自己,不存儲在特定位置。JavaScript中的字面量有:字符串、數字、布爾值、對象、數組、函數、正則表達式,以及特殊的null和undefined值。
  • 本地變量:java

    • 開發人員使用關鍵字var定義的數據存儲單元。
  • 數組元素:正則表達式

    • 存儲在JavaScript數組對象內部,以數字做爲索引。
  • 對象成員:算法

    • 存儲在JavaScript對象內部,以字符串做爲索引。
字面量和局部變量的訪問速度快於數組項和對象成員的訪問速度。

2. 在執行環境的做用域中,一個標識符所在的位置越深,它的讀寫速度也就越慢

若是跨做用域的值在函數中被引用一次以上,那麼就把它存儲到局部變量裏。

3. 動態做用域

  • with:shell

    • 當代碼執行到with語句時,執行環境的做用域鏈臨時被改變了。一個新的變量對象被建立,它包含了參數指定的對象的全部屬性。這個對象被推入做用域鏈的首位。
  • try-catch:編程

    • try代碼塊中發生錯誤,執行過程會自動跳轉到catch子句,而後把異常對象推入一個變量對象並置於做用域的首位。
    • 一旦catch子句執行完畢,做用域鏈就會返回到以前的狀態。
儘可能簡化代碼來使的 catch子句對性能的影響最小化,將錯誤委託給一個函數來處理。
  • eval
只有在確實有必要時才推薦使用動態做用域。

4. 原型

  • 對象能夠有兩種成員類型:實例成員(也稱own成員)和原型成員。json

    • hasOwnProperty()方法能夠判斷對象是否包含特定的實例成員。
    • 要肯定對象是否包含特定的屬性,可使用in操做符,它既會搜索實例也會搜索原型。

小結

數據存儲的位置會對代碼總體性能產生重大的影響。跨域

  • 訪問字面量和局部變量的速度最快,相反,訪問數組元素和對象成員相對較慢。
  • 因爲局部變量存在於做用域鏈的起始位置,所以訪問局部變量比訪問跨做用域變量更快。變量在做用域中的位置越深,訪問所須要時間就越長。因爲全局變量總處於做用域鏈的最末端,所以訪問速度是最慢的。
  • 避免使用with語句,由於它會改變執行環境做用域鏈。一樣,try-catch語句中的catch子句也有一樣的影響,所以也要當心。
  • 嵌套的對象成員會明顯影響性能,儘可能少用。
  • 屬性或方法在原型鏈中的位置越深,訪問它的速度也就越慢。
  • 一般來講,你能夠==經過把經常使用的對象成員、數組元素、跨域變量保存在局部變量中來改善JavaScript性能==,由於局部變量訪問速度更快。

3、DOM編程

1. 文檔對象模型(DOM)是一個獨立於語言的,用於操做XML和HTML文檔的程序接口(API)

2. DOM的渲染和JavaScript引擎是相互獨立的。

兩個相互獨立的東西經過接口鏈接就會很慢,==要儘量減小訪問次數==。

3. 訪問DOM的次數越多,代碼的運行速度就越慢。

通用的法則是:減小訪問DOM的次數,把運算儘可能留在ECMAScript這一端處理。

4. 在大多數瀏覽器中,innerHTML 比DOM方法documnet.createElemnt()要快。

5. 克隆節點element.cloneNode()比建立節點document.createELemnt()要快。

6. 訪問HTML集合

HTML集合是包含了DOM節點引用的類數組對象,一直和文檔保持鏈接,每次訪問都要重複執行查詢的過程,是低效之源。
  • 只須要遍歷一個相對較小的元素,那麼緩存length就夠了。
  • 因爲遍歷數組比遍歷集合快,先將集合元素拷貝到數組中,那麼訪問它的屬性會更快。
  • 使用局部變量替代這些須要屢次讀取的元素

7. 遍歷DOM

  • IE中,nextSiblingchildNode表現優異。
  • 直接使用元素節點選擇器查詢元素,childrenchidNodes快。
  • 使用選擇器API,querySelectorALl()比使用JavaScript和DOM來遍歷查找元素要快。

8. 重繪和重排

  • 當DOM變化影響元素的寬高和佈局改變就會引起瀏覽器「重排(reflow)」。

    • 什麼時候觸發

      • 添加或刪除可見的DOM元素。
      • 元素位置的改變。
      • 元素的尺寸改變(包括:外邊距、內邊距、邊框厚度、寬度、高度等屬性改變)。
      • 內容改變,例如:文本改變或圖片被另外一個不一樣尺寸的圖片代替。
      • 頁面渲染器初始化。
      • 瀏覽器窗口尺寸改變。
      • 滾動條出現。
    • 手動觸發,最好避免使用的屬性

      • offsetTop, offsetLeft, offsetWidth, offsetHeight
      • scrollTop, scrollLeft, scrollWidth, scrollHeight
      • clientTop, clientLeft, clientWidth, clientHeight
      • getComputedStye() (currentStyle in IE)
  • 瀏覽器從新繪製受影響的部分到屏幕中,該過程稱爲「重繪(repaint)」。

9. 最小化重繪和重排

  • 改變樣式
合併全部的改變而後一次處理,這樣只會修改DOM一次。使用 cssText屬性能夠實現
  • 批量修改DOM

當你須要對DOM元素進行一系列操做時,可經過如下步驟來減小重繪和重排的次數:

  1. 使元素脫離文檔流

    • 隱藏元素,應用修改,從新顯示。
    • 使用文檔片斷(document fragment)在當前DOM以外構建一個子樹,再把它拷貝迴文檔。
    • 將原始元素拷貝到一個脫離文檔的節點中,修改副本,完成後再替換原始元素。
  2. 對其應用多重改變。
  3. 把元素帶回文檔中。

10. 讓元素脫離動畫流

使用如下步驟能夠避免頁面中的大部分重排:

  1. 使用絕對位置來定位頁面上的動畫元素,使其脫離文檔流。
  2. 讓元素動起來。當它擴大時,會臨時覆蓋部分頁面。但這只是頁面一個小區域的重繪過程,不會產生重排並重繪頁面的大部份內容。
  3. 當動畫結束時恢復定位,從而只會下移一次文檔的其餘元素。

11. 事件委託

根據DOM標準,每一個事件都要經歷三個階段:

  • 捕獲
  • 到達目標
  • 冒泡

小結

訪問和操做DOM是現代Web應用的重要部分。可是每次穿越鏈接ECMAScript和DOM兩個島嶼之間的橋樑,都會被收取「過橋費」。爲了減小DOM編程帶來的性能損失,請記住如下幾點:

  • 最小化DOM訪問次數,儘量在JavaScript端處理。
  • 若是須要屢次訪問某個DOM節點,請使用局部變量存儲它的引用。
  • 當心處理HTML集合,由於它實時聯繫着底層文檔。把集合的長度緩存到一個變量中,並在迭代中使用它。若是須要常常操做集合,建議把它拷貝到一個數組中。
  • 若是可能的話,使用速度更快的API,好比querySelectorAll()firstElementChild
  • 要留意重繪和重排;批量修改樣式時,「離線」操做DOM樹,使用緩存,並減小訪問佈局信息的次數。
  • 動畫中使用絕對定位,使用拖放代理。
  • 使用事件委託來減小事件處理器的數量。

4、算法和流程控制

1. 循環

  • 循環類型:

    • for循環:由四部分組成:初始化、前側條件、後執行條件、循環體。
    • while循環:最簡單的前側循環,由一個前側條件和一個循環體構成。任何for都能改寫成while循環,反之亦然
    • do-while: do-while是JavaScript中惟一一種後側循環,由循環體和後側條件構成。
    • for-in循環: 枚舉任何對象的屬性名。所包含的屬性包括對象實例屬性以及從原型鏈繼承而來的屬性。for-in循環比其它幾種明顯要慢,除非你須要迭代一個屬性數量未知的對象,不然應該避免使用for-in循環
  • 循環性能:

    • 優化循環:

      • 減小迭代工做量:

        • 只查找一次屬性,並把值存儲到一個局部變量,而後在控制條件中使用這個變量。
        • 經過顛倒數組的順序來提升循環性能。
      • 減小迭代次數: 達夫設備(Dufff's Device)

        var i = item.length % 8;
        while(i){
            process(item[i--]);
        }
        
        i = Math.floor(item.length / 8);
        
        while(i){
            process(item[i--]);
            process(item[i--]);
            process(item[i--]);
            process(item[i--]);
            process(item[i--]);
            process(item[i--]);
            process(item[i--]);
            process(item[i--]);
        }
  • 在全部狀況下,基於循環的迭代比基於函數的迭代(forEach())快8倍

2. 條件語句

  • if-else對比switch: 大多數狀況下switchif-else運行的要快,但只有當條件數量很大時才快的明顯。當條件數量較少時使用if-else,在條件數量較大時使用switch

    • 優化if-else: 最小化到達正確分支前所需判斷的條件數量。最簡單的優化方法是確保最可能出現的條件放在首位。另外一種減小條件判斷次數的方法是把if-else組織成一系列嵌套的if-else語句。(使用二分法把值域分紅一系列的區間)

3. 查找表(數組,對象)

當有大量離散值須要測試時,if-elseswitch比使用查找錶慢不少。

4. 遞歸

爲了能在瀏覽器中安全地工做,建議改用迭代、Memoization,或者結合二者使用。

  • 迭代: 任何遞歸能實現的算法一樣能夠用迭代實現。
  • Memoization
/**
* @params {function} fundamental 須要增長緩存的函數
* @params {object} cache 可選的緩存對象
* @return {function} shell 加入緩存功能的函數
**/
function memoize(fundamental, cache){
    cache = cache || {};
    
    var shell = function(arg){
        if(!cache.hasOwnProperty(arg)){
            cache[arg] = fundamental(arg);
        }
        return cache[arg];
    }
    
    return shell;
}

小結

JavaScript和其它編程語言同樣,代碼的寫法和算法的會影響運行時間。於其餘語言不一樣的是,JavaScript可用資源有限,所以優化技術更爲重要。

  • forwhiledo-while循環性能特性至關,並無一種循環類型明顯快於或慢於其餘類型。
  • 避免使用for-in循環,除非你須要遍歷一個屬性數未知的對象。
  • 改善循環性能的最佳方式是減小每次迭代的運算量和減小循環迭代次數。
  • 一般來說,switch老是比if-else快,但並不老是最佳解決方案。
  • 當判斷條件較多時,使用查找表比if-elseswitch更快。
  • 瀏覽器的調用棧大小限制了遞歸算法在JavaScript中的應用;棧溢出錯誤會致使其它代碼中斷運行。
  • 若是你遇到棧溢出錯誤,可將方法改成迭代算法,或使用Memoization來避免重複計算。

運行的代碼數量越大,使用這些策略所帶來的性能提高也就越明顯。

5、字符串和正則表達式

小結

密集的字符串操做和草率地編寫正則表達式可能產生嚴重的性能障礙,本章提供的建議會幫助你避免這些常見的陷阱。

  • 當鏈接數巨大或尺寸巨大的字符串,數組項合併是惟一在IE 7及更早版本中性能合理的方法。
  • 若是不須要考慮IE 7及更早版本的性能,數據項合併是最慢的字符串鏈接方法之一。推薦使用簡單的+和+=操做符代替,避免沒必要要的中間字符串。
  • 回溯既是正則表達式匹配功能的基本組成部分,也是正則表達式的低效之源。
  • 回溯失控發生在正則表達式本應快速匹配的地方,可是由於某些特殊的字符串匹配動做致使運行緩慢甚至瀏覽器崩潰。避免這個問題的辦法是:是相鄰的字元互斥,避免嵌套量詞對同一字符串的相同部分屢次匹配,經過重複利用預查的原子組去除沒必要要的回溯。
  • 提升正則表達式效率的各類技術手段會有助於正則表達式更快地匹配,並在非匹配位置上花更少的時間(參見:「更多提升正則表達式效率的方法」)。
  • 正則表達式並不老是完成工做的最佳工具,尤爲當你值搜索字面字符串的時候。
  • 儘管有許多方法能夠去除字符串的首位空白,但使用兩個簡單的正則表達式(一個用來去除頭部空白,一個用來去除尾部空白)來處理大量字符串內容能提供一個簡潔而跨瀏覽器的方法。從字符串末尾開始循環向前搜索第一個非空白字符串,或者將此技術同正則表達式結合起來,會提供一個更好的替代方案,它不多受到字符串長度影響。

6、快速響應的用戶界面

1. 瀏覽器UI線程

  • 大多數瀏覽器讓一個單線程共用於執行JavaScript和更新用戶界面。
  • 長時間的JavaScript代碼執行會致使用戶界面更新不及時,對用戶體驗形成不良影響。
  • 瀏覽器會限制JavaScript的運行時間,達到必定限度時會終止JavaScript的運行。
  • JavaScript的運行時間不該該超過100毫秒。
  • 最理想的方法是讓出UI線程的控制權,使得UI能夠更新。

2. 瀏覽器UI線程

  • 使用定時器讓出時間片斷,定時器於UI線程的交互方式有助於把運行耗時較長的腳本拆分爲較短的片斷。
  • 使用定時器處理數組

    • 知足兩個條件:

      • 處理過程不須要同步
      • 數據處理不須要按順序
function processArray(items, process, callback){
    var todo = items.concat(); // 克隆原數組
    
    // 創建一個定時器
    setTimeout(function(){
        process(todo.shift()); // 處理函數
        
        // 數組內若是還有數據,再執行一次當前函數,不然觸發回調函數
        if (todo.length > 0){
            setTimeout(argument.callee, 25);
        }else{
            callback(items);
        }
    }, 25);
}
  • 分割任務

    • 前提條件: 任務能夠異步處理並且不影響用戶體驗形成相關代碼錯誤。
function multistep(steps, args, callback){
    var tasks = step.concat(); // 克隆數組
    
    setTimeout(function(){
        // 執行下一個任務
        var task = tasks.shift();
        task.apply(null, args || []);
        
        // 檢查是否還要其它任務
        if(tasks.length > 0){
            setTimeout(arguments.callee, 25);
        }else{
            callback();
        }
    }, 25);
}
<!--調用方法-->
function saveDocument(id){
    var tasks = [openDocumnet, writeText, closeDocument, updateUI];
    multistep(tasks, [id], function(){
       alert("Save completed!") 
    });
}
  • 記錄代碼運行時間

    • 一般來講批量處理比單個處理要快。改進porcessArray()方法:
function timedProcessArray(items, process, callback){
    var todo = items.concat(); // 克隆原始數組
    
    setTimeout(function(){
        var start = +new Date();
        
        do{
            process(todo.shift());
        }while(todo.length > 0 && (+new Date() - start < 50));
        
        if(todo.length > 0){
            setTimeout(arguments.callee, 25);
        }else{
            callback(items);
        }
    }, 25);
}

3. Web Worker

  • 能使代碼獨立運行且不佔用瀏覽器UI線程的時間。
  • 其功能是JavaScript的一個子集,由以下部分組成:

    • 一個navigator對象,只包含四個屬性:appNameappVersionuser Agentplatform
    • 一個location對象(於window.loacation相同,不過全部屬性都是隻讀的)。
    • 一個self對象,指向worker對象。
    • 一個importScripts()方法,用來加載Worker所用到的外部JavaScript文件。
    • 全部的ECMAScript對象,諸如: ObjectArrayDate等。
    • XMLHttpRequest構造器。
    • setTimeout()setIntervar()方法。
    • 一個close()方法,它能馬上中止Worker運行。
  • 與Worker通訊

    • 使用消息系統,消息系統是網頁和Worker通訊的惟一途徑。
var worker = new Worker("code.js");
// 接受信息的事件處理器
worker.onmessage = function(event){
    alert(event.data); //使用data屬性存放傳入的數據
};
worker.postMessage("Nicholas"); // 發送數據給worker

<!--code.js 內部代碼-->
// 加載外部文件
importScripts("file1.js", "file2.js");

self.onmessage = function(event){
    self.postMessage("Hello" + event.data + "!");
}
  • 實際應用

    • Web Workers適用於那些處理純數據,或者與瀏覽器UI無關的長時間運行腳本。例如:

      • 編碼/解碼大字符串。
      • 複雜數學運算(包括圖像或視頻處理)。
      • 大數組排序。

小結

JavaScript和用戶界面更新在同一個進程中運行,所以一次只能處理一件事。這意味着當JavaScript代碼正在運行時,用戶界面不能響應輸入,反之亦然。高效的管理UI線程就是要確保JavaScript不能運行太長時間,以避免影響用戶體驗。最後,請牢記以下幾點:

  • 任何JavaScript任務都不該該執行超過100毫秒。過長的運行時間會致使UI更新出現明顯的延遲,從而對用戶體驗產生負面影響。
  • JavaScript運行期間,瀏覽器響應用戶交互的行爲存在差別。不管如何,JavaScript長時間運行將致使用戶體驗變得混亂和脫節。
  • 定時器可用來安排代碼延遲執行,它使得你能夠把長時間運行腳本分解成一系列的小任務。
  • Web Worker是新版瀏覽器支持的特性,它容許你在UI線程外部執行JavaScript代碼,從而避免鎖定UI。

Web應用越複雜,積極主動地管理UI線程就越重要。即便JavaScript代碼再重要,也不該該影響用戶體驗。

7、Ajax

1. 數據請求技術

  • XMLHttpRequest(XHR)
  • Dynamic script tag insertion 動態腳本注入
  • iframes
  • Comet
  • MultipartXHR
XMLHttpRequest
  • 容許異步發送和接受數據
  • 能精確地控制發送請求和數據接受
var url = '/data.php';
var params = [
'id=7894',
'limit=20'
];

var req = new XMLHttpRequest();

req.onreadystatechange = function(){
    if(req.redyState === 4){
        var responseHeaders = req.getAllResponseHeaders(); // 獲取響應頭信息
        var data = req.reponseText; // 獲取數據
        // 數據處理
    }
    
    req.open('GET', url + '?' + params.join('&'), ture);
    req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); // 設置請求頭信息
    req.send(null); // 發送一個請求
}
POST和GET對比:當只獲取數據時,因使用GET請求的數據會被瀏覽器緩存,若果屢次請求同一數據的話,它有助於提高性能;當請求的URL加上參數的長度大於等於2048個字符時,才應該使用POST獲取數據(瀏覽器會限制URL長度)。
動態腳本注入
  • 克服了XHR的最大限制:它能跨域請求數據。
  • 限制:

    • 不能設置請求的頭信息。
    • 參數傳遞只能用GET。
    • 不能設置請求的超時處理和重試。
    • 必須等待全部數據都已經返回才能夠訪問他們,
    • 不能訪問請求的頭信息,也不能把整個響應消息做爲字符串來處理。
var scriptElement = doucment.createElement('script');
scriptElement.src = "http://any-domain.com/javascropt/lib.js";
document.getElementsByTagName('head')[0].appendChild(scriptElement);

function jsonnCallback(jsonString){
    var = eval('('+jsonString+')');
    // 處理數據...
}

// lib.js
jsonCallback({ "status": 12, "colors": [ "#fff", "#000", "#ff0000" ] });
速度很快,可是引用外部來源的代碼不安全。
Multipart XHR
  • multipart XHR(MXHR)容許客戶端只用一個HTTP請求就能夠從服務端向客戶端傳送多個資源。
  • 服務器將多個資源拼接成一個長字符串,客戶端接收到數據後進行拆分,能夠在收到資源時就馬上處理。
  • 缺點:這種方式獲取的資源不能被瀏覽器緩存。
  • 在特定場合下使用能顯著提高頁面的總體性能:

    • 頁面包含了大量其餘地方用不到的資源(所以也無需緩存),尤爲是圖片。
    • 網站已經在每一個頁面中使用一個獨立打包的JavaScript或CSS文件以減小HTTP請求;由於對每一個頁面來講這些文件都是惟一的,因此並不須要從緩存中讀取數據,除非重載頁面。
var req = new XMLHttpReques();
var getLatestPacketInterval, lastLength = 0;

req.open('GET', 'rollup_images.php', ture);
req.onreadystatechange = readyStateHandler;
req.send(null);

function readyStateHandler(){
    if(req.readyState === 3 && getLatestPacketInterval == null){
        // 開始輪詢
        getLatestPacketInterval = window.setInterval(function(){
            getLatestPacket();
        }, 15);
    }
    
    if(req.readyState === 4){
        // 中止輪詢
        clearInterval(getLatestPacketInterval);
        
        // 獲取最後一個數據包
        getLatestPacket();
    }
}

function getLatestPacket(){
    var length = req.responseText.length;
    var packet = req.reponseText.substring(lastLength, length);
    
    processPacket(packet);
    lastLength = length;
}

2. 發送數據

  • XHR
  • 信標(beacons)
XMLHttpRequest
function xhrPost(url, params, callback){
    var req = new XMLHttpRequest();
    
    req.onerror = function(){
        setTimeout(function(){
            xhrPost(url, jparams, callback);
        }, 1000);
    };
    
    req.onreadystatechange = function(){
        if(req.readyState == 4){
            if(callback && typeof callback === 'function'){
                callback();
            }
        }
    };
    
    req.open('POST', url, ture);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
    req.setRequestHeader('Content-Type', params.length);
    req.send(params.join('&'));
}
當使用XHR發送數據到服務器時,GEt方式會更快。這是由於,對於少許數據而言,一個GET請求往服務器只發送一個數據包。而一個POST請求,至少要發送兩個數據包,一個裝在頭信息,一個裝載POST正文。POST更適合發送大量數據到服務器,由於它不關心額外數據包的數量。
Beacons
  • 信標是向服務器回傳數據最快且最有效的方式。
  • 若是你只關心發送數據到服務器(可能須要極少的返回信息),那麼請使用圖片信標。
var url = '/status_tarcker.php';
var params = [
    'step=2',
    'time=12316151'
];

var beacon = new Image();
beacon.src = url + '?' + params.join('&');

beacon.onload = function(){
    if(this.width == 1){
        // 成功
    }eles if(this.width == 2){
        // 失敗,請重試並建立另外一個信標
    }
};

beacon.onerror = function(){
    // 出錯, 稍後重試並建立另外一個信標。
};

3. 數據格式

  • 經常使用數據格式:

    • XML
    • XPath
    • JSON
    • JSON-P
    • HTML
    • 自定義格式
  • 一般來講數據格式越輕量越好,JSON和字符分割的自定義格式是最好的。若是數據集很大而且對解析時間有要求,那麼就從以下兩種格式中作出選擇:

    • JSON-P數據, 使用動態腳本注入獲取。它把數據當作可執行JavaScript而不是字符串,解析速度極快。它能跨域使用,但涉及敏感數據時不該該使用它。
    • 字符分割的自定義格式,使用XHR或動態腳本注入獲取,用split()解析。這項技術解析大數據集比JSON-P略快,並且一般文件尺寸更小。

4. Ajax性能指南

  • 緩存數據

    • 在服務端,設置HTTP頭信息以確保你的響應會被瀏覽器緩存。
    • 在客戶端,把獲取到的信息存儲到本地,從而避免再次請求。
    • 第一種技術使用簡單並且好維護,而第二種則給你最大的控制權。
//本地數據存儲:把響應文本保存到一個對象中,以URL爲鍵值做爲索引。
var localCache = {};
function xhrRequest(url, callback){
    // 檢查此URL的本地緩存
   if(localCache[url]){
       callback.success(localCahce[url]);
       return;
   } 
   
   // 此URL對應的緩存沒有找到,則發送請求
   var req = new createXhrObject();
   req.onerror = function(){
       callback.error();
   };
   
   req.onreadystatechange = function(){
       if(req.readyState == 4){
       
           if(req.responseText === '' || req.status == '404'){
               callback.error();
               return;
           }
           
           // 存儲響應文本到本地緩存
           
           localCache[url] = req.responseText;
           callback.success(req.responseText);
       }
   };
   
   req.open("GET", url, true);
   req.send(null);
}

// 刪除緩存
delete localCache['/user/friendlist/'];

小結

高性能的Ajax包括如下方面: 瞭解你項目的具體需求,選擇正確的數據格式和與之匹配的傳輸技術。

做爲數據格式,純文本和HTML只適用於特定場合,但它們能夠節省客戶端的CUP週期。XML被普遍應用並且支持良好,可是它十分笨重並且解析緩慢。JSON是輕量級的,解析速度快(被視爲原生代碼而不是字符串),通用性與XML至關。字符分割的自定義格式十分輕量,在解析大量數據集時很是快,但需編寫額外的服務端構造函數,並在客戶端解析。

當從頁面當前所處的域下請求數據時,XHR提供了最完善的控制和靈活性,儘管它會把接受到的全部數據當成一個字符串,且這有可能下降解析速度。另外一方面,動態腳本注入容許跨域請求和本地執行JavaScript和JSON可是它的接口不那麼安全,並且還不能讀取頭信息或相應代碼。Multiple XHR能夠用來減小請求數,並處理一個響應的各類文件類型,可是它不能緩存接收到的響應。當須要發送數據時,圖片信標是一種簡單而有效的方法。XHR還能夠用POST方法發送大量數據。

除了這些格式和傳輸數據,還有一些準則有助於加速你的Ajax:

  • 減小請求數,能夠經過合併JavaScript和CSS,或使用MHXR。
  • 縮短頁面的加載時間,頁面主要內容加載完成後,用Ajax獲取那些次要的問題。
  • 確保你的代碼錯誤不會輸出給用戶,並在服務端處理錯誤。
  • 知道什麼時候使用成熟的Ajax類庫,以及什麼時候編寫本身的底層Ajax代碼。

Ajax爲提高你的網站的潛在性能提供了廣闊的空間,由於許多網站大量使用異步請求,並且它還提供了一些與它無關的問題的解決方案,好比有過多的資源須要加載。在XHR創造性地使用是一個反應遲鈍且平淡無奇的頁面與響應快速且高效的頁面的區別所在;是一個用戶痛恨使用的站點與用戶迷戀的站點的區別所在。

8、編程實戰

1. 避免雙重求值

在JavaScript代碼中執行另一段代碼時,都會致使雙重求值的性能消耗。

var num1 = 5,
    num2 = 6,
    
    // eval()執行代碼字符串
    result = eval("num1 + num2");
    
    // Function()執行代碼字符串
    sum = new Function("arg1", "arg2", "return arg1 + arg2");
    
    // setTime()執行代碼字符串
    setTime("sum = num1 + num2", 100);
    
    // settInterval() 執行代碼字符串
    setInterval("sum = num1 + num2", 100);
雙重求值是一項代價昂貴的操做,它比直接包含的代碼執行速度要慢許多。

2. 使用Object/Array直接量

使用直接量是建立對象和數組最快的方式。

3. 避免重複工做

別作可有可無的工做,別重複作已經完成的工做。

  • 延遲加載
function addHandler(target, eventType, handler){
    // 覆寫現有函數
    if(target.addEventListener){ // DOM2 Events
        addHandler = function(target, eventType, handler){
            target.addEventListener(eventType, handler, false);
        };
    }else { // IE
        addHandler = function(target, evetType, handler){
            target.attachEvent("on" + eventType, handler);
        };
    }
    
    //調用新函數
    addHandler(target, eventType, handler);
}

function removeHandler(target, eventType, handler){
    // 覆寫現有函數
    if(target.removeEventListener){ //DOM2 Events
        removeHandler = function(target, eventType, handler){
            target.removeEventListener(eventType, handler, false);
        };
    }else{ //IE
        removeHandler = function(target, eventType, handler){
            target.detachEvent("on" + eventType, handler);
        };
    }
    
    // 調用新函數
    removeHanlder(target, eventType, handler);
}
  • 條件預加載
var addHandler = document.body.addEventListenre ?
                function(target, efvent, handler){
                    target.addEventListener(eventTye, handler, false);
                }:
                function(target, eventType, handler){
                    target.attachEvent("on" + eventType, handler);
                };
                
var removeHanlder = document.body.removeEventListener ?
                    function(target, efvent, handler){
                        target.removeEventListener(eventTye, handler, false);
                    }:
                    function(target, eventType, handler){
                        target.detachEvent("on" + eventType, handler);
                    };

4. 使用速度快的部分

  • 位操做
for(var i = 0; i < length; i < len; i++){
    if(i & 1){
        className = "odd";
    }else{
        className = "even";
    }
}
  • 原生方法
常量
Math.E E的值,天然對數的底
Math.LN10 10的天然對數
Math.LN2 2的天然對數
Math.LOG2E 以2爲底的E的對數
Math.LOG10E 以10爲底的E的對數
Math.PI π的常量
Math.SQRT1_2 1/2的平方根
Math.SQRT2 2的平方根
方法 含義
Math.abs(num) 返回num的絕對值
Math.exp(num) 返回E的指數
Math.log(num) 返回num的天然對數
Math.pow(num) 返回num的power次冪
Math.sqrt(num) 返回num的平方根
Math.acos(x) 返回x的反餘弦值
Math.asin(x) 返回x的反正弦值
Math.atan(x) 返回x的反正切值
Math.atan2(y, x) 返回從X軸到(y,x)點的角度
Math.cos(x) 返回x的餘弦值
Math.sin(x) 返回x的正弦值
Math.tan(x) 返回x的正切值

小結

JavaScript提出了一些獨一無二的性能挑戰,這與你組織代碼的方式有關。隨着Web應用變得愈來愈高級,包含的JavaScript代碼也愈來愈多,各類模式和反模式也逐漸出現。爲了編寫更高效的代碼,請牢記如下編程實踐:

  • 經過使用eval()Function()構造器來避免雙重求值帶來的性能消耗。一樣的,給setTimeout()setInterval()傳遞函數而不是字符串做爲參數。
  • 儘可能使用直接量建立對象和數組。直接量的建立和初始化都比非直接量形式要快。
  • 避免作重複的工做。當須要檢測瀏覽器時,可以使用延遲加載或條件預加載。
  • 在進行數學計時,考慮使用直接操做數字的二進制形式的位運算。
  • JavaScript的原生方法總會比你寫的任何代碼都要快。儘可能使用原生方法。

當本書涵蓋了大量的優化技術和方法,把這些方案應用在那些被頻繁使用的代碼上時,你將會看到顯著的性能提高。

9、構建並部署高性能JavaScript應用

小結

構建和部署的過程對基於JavaScript的Web應用的性能有着巨大影響。這個過程當中最重要的步驟有:

  • 合併JavaScript文件以減小HTTP請求數。
  • 使用YUI Compressor壓縮JavaScript文件。
  • 在服務器端壓縮JavaScript文件(Gzip編碼)。
  • 經過正確設置HTTP響應頭來緩存JavaScript文件,經過向文件名增長時間戳來避免緩存問題。
  • 使用CDN(Content Delivery NetWork)提供JavaScript文件;CDN不只能夠提高性能,它也爲你管理文件的壓縮與緩存。

全部這些步驟都應該自動化處理,可使用公開的工具,好比Apache Ant,也可使用定製的工具來知足你的特定需求。若是你使得構建工程工做起來,你將會顯著提升那些依賴大量JavaScript的Web應用或網站的性能。

10、工具

小結

當網頁或Web應用變慢時,分析從網絡下載的資源以及分析腳本的運行性能能讓你專一於那些最須要優化的地方。

  • 使用網絡分析工具找出加載腳本和頁面中其餘資源換的瓶頸,這會幫助你決定哪些腳本須要延遲加載,或者須要進一步分析。
  • 儘管傳統的經驗告訴咱們要儘可能減小HTTP請求數,但把腳本儘量延遲加載能夠加快頁面渲染速度,給用戶帶來更好的體驗。
  • 使用性能分析工具找出腳本運行過程當中速度慢的地方,檢查每一個函數所消耗的時間,以及函數被調用的次數,經過調用棧自身提供的一些線索來找出須要集中精力優化的地方。
  • 儘管消耗的時間和調用次數一般是數據中最有價值的部分,但仔細觀察函數的調用過程,你也許會發現其餘優化目標。

這些工具會幫助你深刻了解你的代碼在那些一般你比較陌生的編程環境下是如何運行的。在開始優化工做以前先使用它們,以確保開發時間在刀刃上。

相關文章
相關標籤/搜索