javascript性能優化技巧

春節在家,把《高性能的JavaScript》刷了一遍,受益不淺。本着每看完一本書都要作讀書筆記的習慣,將書中的知識點總結一下。javascript

因爲不一樣瀏覽器使用的JavaScript引擎不一樣,所以對JavaScript的優化也不盡相同。也所以,有些方法在IE上可能性能相差很大,但在chrome上相差無幾,也甚至某些方法在IE上最快,但在chrome上卻並非最優的方案,因此,對性能有極致要求的應用,應考慮你的產品使用者最經常使用的瀏覽器。固然,下面提到的優化方法都是通用法則或者對大多數瀏覽器都友好的方法。css

JavaScript加載和執行

JavaScript的下載和執行會阻塞用戶界面的繪製和其餘資源的下載html

優化方法:

1.阻塞式腳本:合併文件(減小http請求),將script標籤放在body尾部(減小頁面css,html的下載阻塞,減小界面的空白時間(瀏覽器在解析到script標籤以前,不會渲染頁面的任何部分))前端

目前流行的構建工具,如webpack,gulp,都有打包、合併文件的功能。java

2.無阻塞式腳本:延遲腳本和動態腳本均不阻塞,即下載過程不阻塞其餘進程node

延遲腳本:
defer和async屬性:都是並行下載,下載過程不阻塞,區別在於執行時機,async是下載完成後當即執行;defer是等頁面加載完成後再執行。defer僅當src屬性聲明時才生效(HTML5的規範)webpack

動態腳本:
動態添加script標籤,返回的代碼一般會馬上執行,因此,爲了確保腳本下載完成且準備就緒後才執行,須偵聽load事件。將script添加到head中比添加到body中更保險。web

//動態添加腳本,當腳本下載完成且準備就緒後執行回調函數。(這也是推薦的無阻塞的方法)
function loadScript(url,callback){
    var script=document.creatElement('script');
    script.type='text/javascript';
    if(script.readyState){ //IE
        script.onreadystatechange=function(){
            if(script.readyState == 'loaded' || script.readyState == 'complete'){
                script.onreadystatechange=null;
                callback();
            }
        }
    }else{  //非IE
        script.onload=function(){
            callback();
        }
    }

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

}

數據存取

將全局變量存儲到局部變量中:由於全局變量老是存在於執行環境做用域鏈的最末端,因此,訪問全局變量是最慢的,訪問局部變量是最快的。尤爲是對於未優化過的JavaScript引擎。ajax

在JavaScript中,只有2個語句能夠在執行時臨時改變做用域鏈:with語句和try-catch的catch子句。with語句會使得局部變量位於做用域第二層,會使性能降低,因此應避免使用。try-catch權衡使用(由於可預測的錯誤說明代碼有問題,應及早修復)。正則表達式

儘可能避免使用with,try-catch,eval等動態做用域語句,由於JavaScript引擎沒法經過靜態分析的方法進行優化。

閉包會影響性能(做用域鏈加深)和可能致使內存泄漏(IE中)

總結:

  1. 使用對象字面量代替對象

  2. 使用局部變量存儲全局變量和對象成員

  3. 儘可能不用with,eval語句,try-catch的catch子句要謹慎使用

  4. 嵌套越深,性能越差,儘可能少用。

DOM編程

DOM和JavaScript是2個獨立的功能,只經過API鏈接,用JavaScript操做DOM天生就慢,因此應儘可能減小用JavaScript操做DOM。

原則:

1.減小訪問DOM的次數,把運算儘可能留在ECMAScript這一端處理。
2.innerHTML在絕大多數瀏覽器中比原生DOM方法要快(最新版的chrome除外),推薦使用。
3.用element.cloneNode()代替document.createElement(),稍快一些。
4.緩存HTML集合的length.

//這會是一個死循環,由於取HTML集合的length會重複執行查詢的過程。
    var addDivs=document.getElementsByTagName('div');
    for(var i=0,len=addDivs.length;i<len;i++){
        document.body.appendChild(document.createElement('div'));
    }

5.使用children代替childNodes,由於childNodes會包含文本節點(空格)和註釋節點,還須要本身額外過濾這些節點,children已經幫咱們過濾掉這些節點了,並且使用的過濾方法效率很高。
6.原生選擇器API:querySelectorAll()和querySelector() ,IE8及以上支持
querySelectorAll()返回的是個nodelist(也是類數組),不是HTML集合(與getElenmentsByTagName等不一樣)。
7.減小重繪和重排:
在修改樣式的過程當中,最好避免使用下面的屬性,由於它們會刷新渲染隊列,儘可能少查詢下列屬性,能夠用局部變量緩存結果。

offsetTop,offsetLeft,offsetWidth,offsetHeight,
scrollTop,scrollLeft,scrollWidth,scrollHeight
clientTop,clientLeft,clientWidth,clientHeight
getComputedStyle()  (currentStyle in IE)

8.合併屢次對DOM和樣式的修改:

el.style.cssText+=';border-left:2px;';
JavaScript改變class

9.批量修改DOM時,使用document fragment:文檔片斷是一個輕量級的document對象,它自己就是爲了更新和移動節點設計的。

var fragement=document.createDocumentFragment();
var li=document.createElement('li');
fragement.appendChild(li);
document.body.appendChild(fragement);

10.動畫中使用絕對定位,使用拖放代理。
11.使用事件委託來減小事件處理器的數量。

ps:我的以爲,原生方法和庫封裝的方法並不衝突,應根據實際狀況和我的的技能掌握狀況選擇最合適的方法。

算法和流程控制

  1. for...in的循環性能最差(由於它須要搜索實例和原型上的全部屬性),除非,你須要遍歷一個屬性數量未知的對象,不然不要使用它。
    更不要用它遍歷數組成員。其他的循環性能都差很少。

  2. 倒序循環,把減法操做放到控制條件中,例如:k--,這樣只是比較「它是true嗎?」速度更快。

  3. forEach()比數組循環慢,若是對性能有極致要求,仍是用數組循環好。

  4. 當判斷值多於2個時,使用switch,不然用if-else (數量少時,性能差異不大,可根據我的喜愛使用)。若判斷值不少,且沒有什麼複雜的操做,能夠用數組代替switch。
    在JavaScript中,switch使用全等操做符,不會發生類型轉換的損耗。

  5. 把最可能出現的條件放在首位。

  6. 調用棧溢出錯誤基本都是由遞歸致使的:不正確的終止條件;包含了太多遞歸,超過了瀏覽器的調用棧限制。把遞歸算法改用迭代算法實現是避免調用棧溢出錯誤的解決方法之一。

  7. 緩存:避免重複性工做,手動實現緩存(Vue源碼中就有不少緩存)

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];
  }

字符串和正則表達式

字符串拼接推薦用+ +=,推薦寫法:str=str+'one'+"two";(將str寫在左側)

書上說:在大多數瀏覽器中,Array.prototype.join()比其餘字符串鏈接方法更慢,但在IE7及早期的瀏覽器中,在合併大量字符串時是最高效的途徑。

每一個瀏覽器都有它本身的正則表達式引擎,它們有着各自的優點。

提升正則表達式效率的方法

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

  2. 正則表達式以簡單,必需的字元開始:例如:起始標記是^,特定字符串,[a-z]或者d等,避免以分組或選擇字元開頭,避免/one|two/頂層分支。

  3. 減小分支數量,縮小分支範圍:例如:將cat|bat 替換爲:[cb]at ;將red|read 替換爲:rea?d 將red|raw 替換爲:r(?:ed|aw) 將(.|r|n)替換爲:[sS]。

  4. 當分支必不可少時,將經常使用分支放到前面。

  5. 使用非捕獲組

  6. 合理使用捕獲:若是須要引用匹配的一部分,應用捕獲,而後引用那部分

  7. 暴露必須的字元:用/^(ab|cd)/代替/(^ab|^cd)/

  8. 使用合適的量詞:貪婪和惰性量詞的匹配過程不同,視狀況選擇使用。

  9. 將正則表達式賦值給變量(以免對正則從新編譯)並重用它們。

  10. 將複雜的正則拆分爲簡單的片斷:若是太複雜,能夠先用條件判斷分割

//去除字符串首尾空格的方法,推薦寫法
if(!String.prototype.trim){    //防止覆蓋原生方法
        String.prototype.trim=function(){
            return this.replace(/^\s+/,'').replace(/\s+$/,'');
        }
   }

儘管正則很強大,但也不是任什麼時候候都要用正則。對於字面量字符串的操做,字符串原生的方法就很快,例如:indexOf,slice,substring等。

其餘

  1. 建議定時器最小延遲時間是25ms.小於10ms時,各瀏覽器表現不一致。

  2. 多個定時器時,用setInterval()代替多個setTimeout()

  3. 使用動態腳本注入(json-p),要當心第三方域代碼的安全性。不要把敏感信息編碼在json-p中。即使是帶有隨機URL或作了cookie判斷。

  4. 圖片信標:只是用來發送簡單數據

    //只是建立一個Image對象,並不把img插入DOM中。
    (new Image()).src=url+params.join('&');
  5. 儘量使用JOSN.parse()解析json字符串,該方法能夠捕獲json字符串中的詞法錯誤,並容許傳入一個函數用來過濾或轉換解析結果。

  6. ajax類庫的侷限:ajax類庫爲了兼容瀏覽器,因此不能訪問XMLHttpRequests的完整功能。例如不能直接訪問readystatechange事件,因此要了解原生的寫法。
    因此,要知道什麼時候使用成熟的類庫,什麼時候編寫本身的底層代碼。

  7. 縮短頁面的加載時間,頁面主要內容加載完成後,再用ajax獲取那些次要的文件。(首頁優化)

  8. 經過正確設置響應頭來緩存JavaScript文件。

  9. 使用位操做,速度快。

i%2   
    //能夠改寫成位運算 &1 :
    if(i&1){ 
        //奇數
    }else{ 
        //偶數
    }
    //位掩碼:後臺經常使用的按位打標,
    var ops=op_a | op_b | op_c;  
    if(ops & op_a){ 
        //op_a存在
    }

我的感想

性能提高有多方面:客戶端性能,網絡狀況,服務器性能,在具體解決及分析問題時,要從各個方面考慮,JavaScript代碼質量,http請求數也只是其中一部分而已,要全面考慮。在進行優化時,要弄清楚性能瓶頸,而後對症優化。

新看到一篇很棒的文章:
前端性能優化備忘錄:https://www.w3ctech.com/topic...

ps:若有不對,歡迎指正。