前端性能優化指南

前端性能優化指南

AJAX優化


  • 緩存AJAXjavascript

    • 異步並不等於即時php

  • 請求使用GETcss

    • 當使用XMLHttpRequest時,而URL長度不到2K,可使用GET請求數據,GET相比POST更快速。html

      • POST類型請求要發送兩個TCP數據包。前端

        • 先發送文件頭。java

        • 再發送數據。node

      • GET類型請求只須要發送一個TCP數據包。mysql

        • 取決於你的cookie數量。jquery

COOKIE專題


  • 減小COOKIE的大小。c++

  • 使用無COOKIE的域。

    • 好比圖片CSS等靜態文件放在靜態資源服務器上並配置單獨域名,客戶端請求靜態文件的時候,減小COOKIE反覆傳輸時對主域名的影響。

DOM優化


  • 優化節點修改。

    • 使用cloneNode在外部更新節點而後再經過replace與原始節點互換。

      var orig = document.getElementById('container');
      var clone = orig.cloneNode(true);
      var list = ['foo', 'bar', 'baz'];
      var content;
      for (var i = 0; i < list.length; i++) {
      content = document.createTextNode(list[i]);
      clone.appendChild(content);
      }
      orig.parentNode.replaceChild(clone, orig);

  • 優化節點添加

    多個節點插入操做,即便在外面設置節點的元素和風格再插入,因爲多個節點仍是會引起屢次reflow。

    • 優化的方法是建立DocumentFragment,在其中插入節點後再添加到頁面。

      • JQuery中全部的添加節點的操做如append,都是最終調用DocumentFragment來實現的,

        createSafeFragment(document) {

        var list = nodeNames.split( "|" ),

        safeFrag = document.createDocumentFragment();

        if (safeFrag.createElement) {

        while (list.length) {
            safeFrag.createElement(
                list.pop();
            );
        };

        };
        return safeFrag;

        };
  • 優化CSS樣式轉換。

    若是須要動態更改CSS樣式,儘可能採用觸發reflow次數較少的方式。

    • 如如下代碼逐條更改元素的幾何屬性,理論上會觸發屢次reflow

      element.style.fontWeight = 'bold' ;
          element.style.marginLeft= '30px' ;
          element.style.marginRight = '30px' ;
    • 能夠經過直接設置元素的className直接設置,只會觸發一次reflow

      element.className = 'selectedAnchor' ;
  • 減小DOM元素數量

    • console中執行命令查看DOM元素數量。

      `document.getElementsByTagName( '*' ).length`
    • 正常頁面的DOM元素數量通常不該該超過1000

    • DOM元素過多會使DOM元素查詢效率,樣式表匹配效率下降,是頁面性能最主要的瓶頸之一。

  • DOM操做優化。

    • DOM操做性能問題主要有如下緣由。

      • DOM元素過多致使元素定位緩慢。

      • 大量的DOM接口調用。

        • JAVASCRIPTDOM之間的交互須要經過函數API接口來完成,形成延時,尤爲是在循環語句中。

      • DOM操做觸發頻繁的reflow(layout)repaint

      • layout發生在repaint以前,因此layout相對來講會形成更多性能損耗。

        • reflow(layout)就是計算頁面元素的幾何信息。

        • repaint就是繪製頁面元素。

      • DOM進行操做會致使瀏覽器執行迴流reflow

    • 解決方案。

      • JAVASCRIPT執行時間是很短的。

      • 最小化DOM訪問次數,儘量在js端執行。

      • 若是須要屢次訪問某個DOM節點,請使用局部變量存儲對它的引用。

      • 謹慎處理HTML集合(HTML集合實時連繫底層文檔),把集合的長度緩存到一個變量中,並在迭代中使用它,若是須要常常操做集合,建議把它拷貝到一個數組中。

      • 若是可能的話,使用速度更快的API,好比querySelectorAllfirstElementChild

      • 要留意重繪和重排。

      • 批量修改樣式時,離線操做DOM樹。

      • 使用緩存,並減小訪問佈局的次數。

      • 動畫中使用絕對定位,使用拖放代理。

      • 使用事件委託來減小事件處理器的數量。

  • 優化DOM交互

    JAVASCRIPT中,DOM操做和交互要消耗大量時間,由於它們每每須要從新渲染整個頁面或者某一個部分。

    • 最小化現場更新

      • 當須要訪問的DOM部分已經已經被渲染爲頁面中的一部分,那麼DOM操做和交互的過程就是再進行一次現場更新

        • 現場更新是須要針對現場(相關顯示頁面的部分結構)當即進行更新,每個更改(無論是插入單個字符仍是移除整個片斷),都有一個性能損耗。

        • 現場更新進行的越多,代碼完成執行所花的時間也越長。

    • 多使用innerHTML

      • 有兩種在頁面上建立DOM節點的方法:

        • 使用諸如createElement()appendChild()之類的DOM方法。

        • 使用innerHTML

          • 當使用innerHTML設置爲某個值時,後臺會建立一個HTML解釋器,而後使用內部的DOM調用來建立DOM結構,而非基於JAVASCRIPTDOM調用。因爲內部方法是編譯好的而非解釋執行,故執行的更快。

        對於小的DOM更改,二者效率差很少,但對於大的DOM更改,innerHTML要比標準的DOM方法建立一樣的DOM結構快得多。

  • 迴流reflow

    • 發生場景。

      • 改變窗體大小。

      • 更改字體。

      • 添加移除stylesheet塊。

      • 內容改變哪怕是輸入框輸入文字。

      • CSS虛類被觸發如 :hover。

      • 更改元素的className。

      • 當對DOM節點執行新增或者刪除操做或內容更改時。

      • 動態設置一個style樣式時(好比element.style.width="10px")。

      • 當獲取一個必須通過計算的尺寸值時,好比訪問offsetWidth、clientHeight或者其餘須要通過計算的CSS值。

    • 解決問題的關鍵,就是限制經過DOM操做所引起迴流的次數。

      • 在對當前DOM進行操做以前,儘量多的作一些準備工做,保證N次建立,1次寫入。

      • 在對DOM操做以前,把要操做的元素,先從當前DOM結構中刪除:

        • 經過removeChild()或者replaceChild()實現真正意義上的刪除。

        • 設置該元素的display樣式爲「none」。

      • 每次修改元素的style屬性都會觸發迴流操做。

        element.style.backgroundColor = "blue";

        • 使用更改className的方式替換style.xxx=xxx的方式。

        • 使用style.cssText = '';一次寫入樣式。

        • 避免設置過多的行內樣式。

        • 添加的結構外元素儘可能設置它們的位置爲fixedabsolute

        • 避免使用表格來佈局。

        • 避免在CSS中使用JavaScript expressions(IE only)

      • 將獲取的DOM數據緩存起來。這種方法,對獲取那些會觸發迴流操做的屬性(好比offsetWidth等)尤其重要。

      • 當對HTMLCollection對象進行操做時,應該將訪問的次數儘量的降至最低,最簡單的,你能夠將length屬性緩存在一個本地變量中,這樣就能大幅度的提升循環的效率。

eval優化


  • 避免eval

    • eval會在時間方面帶來一些效率,但也有不少缺點。

      • eval會致使代碼看起來更髒。

      • eval會須要消耗大量時間。

      • eval會逃過大多數壓縮工具的壓縮。

HTML優化


  • 插入HTML

    • JavaScript中使用document.write生成頁面內容會效率較低,能夠找一個容器元素,好比指定一個div,並使用innerHTML來將HTML代碼插入到頁面中。

  • 避免空的srchref

    • link標籤的href屬性爲空、script標籤的src屬性爲空的時候,瀏覽器渲染的時候會把當前頁面的URL做爲它們的屬性值,從而把頁面的內容加載進來做爲它們的值。

  • 爲文件頭指定Expires

    • 使內容具備緩存性,避免了接下來的頁面訪問中沒必要要的HTTP請求。

  • 重構HTML,把重要內容的優先級提升。

  • Post-load(次要加載)不是必須的資源。

  • 利用預加載優化資源。

  • 合理架構,使DOM結構儘可能簡單。

  • 利用LocalStorage合理緩存資源。

  • 儘可能避免CSS表達式和濾鏡。

  • 嘗試使用defer方式加載Js腳本。

  • 新特性:will-change,把即將發生的改變預先告訴瀏覽器。

  • 新特性Beacon,不堵塞隊列的異步數據發送。

  • 不一樣之處:網絡緩慢,緩存更小,不使人滿意的瀏覽器處理機制。

  • 儘可能多地緩存文件。

  • 使用HTML5 Web Workers來容許多線程工做。

  • 爲不一樣的Viewports設置不一樣大小的Content。

  • 正確設置可Tap的目標的大小。

  • 使用響應式圖片。

  • 支持新接口協議(如HTTP2)。

  • 將來的緩存離線機制:Service Workers。

  • 將來的資源優化Resource Hints(preconnect, preload, 和prerender)。

  • 使用Server-sent Events。

  • 設置一個Meta Viewport。

JITGC優化


  • untyped(無類型)。

    • JAVASCRIPT是個無類型的語言,這致使瞭如x=y+z這種表達式能夠有不少含義。

      • yz是數字,則+表示加法。

      • yz是字符串,則+表示字符串鏈接。

      而JS引擎內部則使用「細粒度」的類型,好比:

      • 32-bit* integer。

      • 64-bit* floating-point。

      這就要求js類型-js引擎類型,須要作「boxed/unboxed(裝箱/解箱)」,在處理一次x=y+z這種計算,須要通過的步驟以下。

      1. 從內存,讀取x=y+z的操做符。

      2. 從內存,讀取yz

      3. 檢查y,z類型,肯定操做的行爲。

      4. unbox y,z

      5. 執行操做符的行爲。

      6. box x

      7. x寫入內存。

      只有第5步驟是真正有效的操做,其餘步驟都是爲第5步驟作準備/收尾,JAVASCRIPTuntyped特性很好用,但也爲此付出了很大的性能代價。

  • JIT

    • 先看看JITuntyped的優化,在JIT下,執行x=y+z流程。

      1. 從內存,讀取x=y+z的操做符。

      2. 從內存,讀取 yz

      3. 檢查yz類型,肯定操做的行爲。

      4. unbox y,z

      5. 執行 操做符 的行爲。

      6. box x

      7. x寫入內存。

      其中12步驟由CPU負責,7步驟JIT把結果保存在寄存器裏。但惋惜不是全部狀況都能使用JIT,當number+numberstring+string 等等可使用JIT,但特殊狀況,如:number+undefined就不行了,只能走舊解析器。

    • 新引擎還對「對象屬性」訪問作了優化,解決方案叫inline caching,簡稱:IC。簡單的說,就是作cache。但若是當list很大時,這種方案反而影響效率。

  • Type-specializing JIT

    Type-specializing JIT引擎用來處理typed類型(聲明類型)變量,但JAVASCRIPT都是untype類型的。

    • Type-specializing JIT的解決方案是:

      • 先經過掃描,監測類型。

      • 經過編譯優化(優化對象不只僅只是「類型」,還包括對JS代碼的優化,但核心是類型優化),生成類型變量。

      • 再作後續計算。

    • Type-specializing JIT的執行x=y+z流程:

      • 從內存,讀取x=y+z的操做符。

      • 從內存,讀取yz

      • 檢查yz類型,肯定操做的行爲。

      • unbox y,z

      • 執行操做符的行爲。

      • box x

      • x寫入內存。

    代價是:

    • 前置的掃描類型

    • 編譯優化。

    因此·Type-specializing JIT·的應用是有選擇性,選擇使用這個引擎的場景包括:

    • 熱點代碼。

    • 經過啓發式算法估算出來的有價值的代碼。

    另外,有2點也須要注意:

    • 當變量類型 發生變化時,引擎有2種處理方式:

      • 少許變動,重編譯,再執行。

      • 大量變動,交給JIT執行。

    • 數組object properties, 閉包變量 不在優化範疇之列。

js載入優化


  • 加快JavaScript裝入速度的工具:

    • Lab.js

      • 藉助LAB.js(裝入和阻止JavaScript),你就能夠並行裝入JavaScript文件,加快總的裝入過程。此外,你還能夠爲須要裝入的腳本設置某個順序,那樣就能確保依賴關係的完整性。此外,開發者聲稱其網站上的速度提高了2倍。

  • 使用適當的CDN:

    • 如今許多網頁使用內容分發網絡(CDN)。它能夠改進你的緩存機制,由於每一個人均可以使用它。它還能爲你節省一些帶寬。你很容易使用ping檢測或使用Firebug調試那些服務器,以便搞清能夠從哪些方面加快數據的速度。選擇CDN時,要照顧到你網站那些訪客的位置。記得儘量使用公共存儲庫。

  • 網頁末尾裝入JavaScript:

    • 也能夠在頭部分放置須要裝入的一些JavaScript,可是前提是它以異步方式裝入。

  • 異步裝入跟蹤代碼:

    腳本加載與解析會阻塞HTML渲染,能夠經過異步加載方式來避免渲染阻塞,步加載的方式不少,比較通用的方法以下。

    var _gaq = _gaq || []; 
        _gaq.push(['_setAccount', 'UA-XXXXXXX-XX']); 
        _gaq.push(['_trackPageview']); 
    (function() { 
        var ga = document.createElement('script'); ga.type = 'text/JavaScript'; ga.async = true; 
        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; 
        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); 
    })();

或者

function loadjs (script_filename){
         var script = document.createElement( 'script' );
         script.setAttribute( 'type' , 'text/javascript' );
         script.setAttribute( 'src' , script_filename);
         script.setAttribute( 'id' , 'script-id' );

         scriptElement = document.getElementById( 'script-id' );
         if (scriptElement){
             document.getElementsByTagName( 'head' )[0].removeChild(scriptElement);
         }
         document.getElementsByTagName( 'head' )[0].appendChild(script);
    }
    var script = 'scripts/alert.js' ;
    loadjs(script);
  • 把你的JavaScript打包成PNG文件

    • 將JavaScript/css數據打包成PNG文件。以後進行拆包,只要使用畫布API的getImageData()。能夠在不縮小數據的狀況下,多壓縮35%左右。並且是無損壓縮,對比較龐大的腳原本說,在圖片指向畫布、讀取像素的過程當中,你會以爲有「一段」裝入時間。

  • 設置Cache-Control和Expires頭

    經過Cache-Control和Expires頭能夠將腳本文件緩存在客戶端或者代理服務器上,能夠減小腳本下載的時間。

    Expires格式:

    Expires = "Expires" ":" HTTP-date
    Expires: Thu, 01 Dec 1994 16:00:00 GMT
    Note: if a response includes a Cache-Control field with the max-age directive that directive overrides the
    Expires field.

    Cache-Control格式:

    Cache-Control   = "Cache-Control" ":" 1#cache-directive
    Cache-Control: public

具體的標準定義能夠參考http1.1中的定義,簡單來講Expires控制過時時間是多久,Cache-Control控制什麼地方能夠緩存 。

with優化


  • 儘量地少用with語句,由於它會增長with語句之外的數據的訪問代價。

  • 避免使用with

    >
    `with`語句將一個新的可變對象推入做用域鏈的頭部,函數的全部局部變量如今處於第二個做用域鏈對象中,從而使局部變量的訪問代價提升。

    var person = {

    name: 「Nicholas",
     age: 30

    }
    function displayInfo() {

    var count = 5;
     with (person) {
         alert(name + ' is ' + age);
         alert( 'count is ' + count);
     }

    }

變量專題


  • 全局變量

    • 當一個變量被定義在全局做用域中,默認狀況下JAVASCRIPT引擎就不會將其回收銷燬。如此該變量就會一直存在於老生代堆內存中,直到頁面被關閉。

    • 全局變量缺點。

      • 使變量不易被回收。

        • 多人協做時容易產生混淆。

      • 在做用域鏈中容易被幹擾。

    • 能夠經過包裝函數來處理全局變量

  • 局部變量。

    • 儘可能選用局部變量而不是全局變量。

    • 局部變量的訪問速度要比全局變量的訪問速度更快,由於全局變量實際上是window對象的成員,而局部變量是放在函數的棧裏的。

  • 手工解除變量引用

    • 在業務代碼中,一個變量已經肯定再也不須要了,那麼就能夠手工解除變量引用,以使其被回收。

      var data = { / some big data / };
      // ...
      data = null;

  • 變量查找優化。

    • 變量聲明帶上var,若是聲明變量忘記了var,那麼JAVASCRIPT引擎將會遍歷整個做用域查找這個變量,結果無論找到與否,都會形成性能損耗。

      • 若是在上級做用域找到了這個變量,上級做用域變量的內容將被無聲的改寫,致使莫名奇妙的錯誤發生。

      • 若是在上級做用域沒有找到該變量,這個變量將自動被聲明爲全局變量,然而卻都找不到這個全局變量的定義。

    • 慎用全局變量。

      • 全局變量須要搜索更長的做用域鏈。

      • 全局變量的生命週期比局部變量長,不利於內存釋放。

      • 過多的全局變量容易形成混淆,增大產生bug的可能性。

    • 具備相同做用域變量經過一個var聲明。

      jQuery.extend = jQuery.fn.extend = function () {
          var options, 
              name, 
              src, 
              copy, 
              copyIsArray, 
              clone,target = arguments[0] || {},
              i = 1,
              length = arguments.length,
              deep = false ;
      }
    • 緩存重複使用的全局變量。

      • 全局變量要比局部變量須要搜索的做用域長

      • 重複調用的方法也能夠經過局部緩存來提速

      • 該項優化在IE上體現比較明顯

        var docElem = window.document.documentElement,

        selector_hasDuplicate,
        matches = docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector ||docElem.msMatchesSelector,
        selector_sortOrder = function ( a, b ) {
            // Flag for duplicate removal
            if ( a === b ) {
                 selector_hasDuplicate = true ;
                 return 0;
            }
        }
  • 善用回調。

    • 除了使用閉包進行內部變量訪問,咱們還可使用如今十分流行的回調函數來進行業務處理。

      function getData(callback) {
            var data = 'some big data';
            callback(null, data);
          }
          getData(function(err, data) {
            console.log(data);
          });
      • 回調函數是一種後續傳遞風格(Continuation Passing Style, CPS)的技術,這種風格的程序編寫將函數的業務重點從返回值轉移到回調函數中去。並且其相比閉包的好處也有不少。

        • 若是傳入的參數是基礎類型(如字符串、數值),回調函數中傳入的形參就會是複製值,業務代碼使用完畢之後,更容易被回收。

        • 經過回調,咱們除了能夠完成同步的請求外,還能夠用在異步編程中,這也就是如今很是流行的一種編寫風格。

        • 回調函數自身一般也是臨時的匿名函數,一旦請求函數執行完畢,回調函數自身的引用就會被解除,自身也獲得回收。

常規優化


  • 傳遞方法取代方法字符串

    一些方法例如setTimeout()setInterval(),接受字符串或者方法實例做爲參數。直接傳遞方法對象做爲參數來避免對字符串的二次解析。

    • 傳遞方法

      setTimeout(test, 1);
    • 傳遞方法字符串

      setTimeout('test()', 1);
  • 使用原始操做代替方法調用

    方法調用通常封裝了原始操做,在性能要求高的邏輯中,可使用原始操做代替方法調用來提升性能。

    • 原始操做

      var min = a<b?a:b;

    • 方法實例

      var min = Math.min(a, b);

  • 定時器

    若是針對的是不斷運行的代碼,不該該使用setTimeout,而應該是用setIntervalsetTimeout每次要從新設置一個定時器。

  • 避免雙重解釋

JAVASCRIPT代碼想解析JAVASCRIPT代碼時就會存在雙重解釋懲罰,雙重解釋通常在使用eval函數、new Function構造函數和setTimeout傳一個字符串時等狀況下會遇到,如。

eval("alert('hello world');");
    var sayHi = new Function("alert('hello world');");
    setTimeout("alert('hello world');", 100);

上述alert('hello world');語句包含在字符串中,即在JS代碼運行的同時必須新啓運一個解析器來解析新的代碼,而實例化一個新的解析器有很大的性能損耗。

咱們看看下面的例子:

    var sum, num1 = 1, num2 = 2;
    /**效率低**/
    for(var i = 0; i < 10000; i++){
        var func = new Function("sum+=num1;num1+=num2;num2++;");
        func();
        //eval("sum+=num1;num1+=num2;num2++;");
    }
    /**效率高**/
    for(var i = 0; i < 10000; i++){
        sum+=num1;
        num1+=num2;
        num2++;
    }

第一種狀況咱們是使用了new Function來進行雙重解釋,而第二種是避免了雙重解釋。

  • 原生方法更快

    • 只要有可能,使用原生方法而不是自已用JS重寫。原生方法是用諸如C/C++之類的編譯型語言寫出來的,要比JS的快多了。

  • 最小化語句數

    JS代碼中的語句數量也會影響所執行的操做的速度,完成多個操做的單個語句要比完成單個操做的多個語句塊快。故要找出能夠組合在一塊兒的語句,以減來總體的執行時間。這裏列舉幾種模式

    • 多個變量聲明

      /不提倡/
      var i = 1;
      var j = "hello";
      var arr = [1,2,3];
      var now = new Date();
      /提倡/
      var i = 1,

      j = "hello",
      arr = [1,2,3],
      now = new Date();
    • 插入迭代值

      /不提倡/
      var name = values[i];
      i++;
      /提倡/
      var name = values[i++];

    • 使用數組和對象字面量,避免使用構造函數Array(),Object()

      /不提倡/
      var a = new Array();
      a[0] = 1;
      a[1] = "hello";
      a[2] = 45;
      var o = new Obejct();
      o.name = "bill";
      o.age = 13;
      /提倡/
      var a = [1, "hello", 45];
      var o = {

      name : "bill",
      age : 13

      };

  • 避免使用屬性訪問方法

    • JavaScript不須要屬性訪問方法,由於全部的屬性都是外部可見的。

    • 添加屬性訪問方法只是增長了一層重定向 ,對於訪問控制沒有意義。

      使用屬性訪問方法示例

      function Car() {
      this .m_tireSize = 17;
      this .m_maxSpeed = 250;
      this .GetTireSize = Car_get_tireSize;
      this .SetTireSize = Car_put_tireSize;
      }

      function Car_get_tireSize() {
      return this .m_tireSize;
      }

      function Car_put_tireSize(value) {
      this .m_tireSize = value;
      }
      var ooCar = new Car();
      var iTireSize = ooCar.GetTireSize();
      ooCar.SetTireSize(iTireSize + 1);

      直接訪問屬性示例

      function Car() {
      this .m_tireSize = 17;
      this .m_maxSpeed = 250;
      }
      var perfCar = new Car();
      var iTireSize = perfCar.m_tireSize;
      perfCar.m_tireSize = iTireSize + 1;

  • 減小使用元素位置操做

    • 通常瀏覽器都會使用增量reflow的方式將須要reflow的操做積累到必定程度而後再一塊兒觸發,可是若是腳本中要獲取如下屬性,那麼積累的reflow將會立刻執行,已獲得準確的位置信息。

      offsetLeft
           offsetTop
           offsetHeight
           offsetWidth
           scrollTop/Left/Width/Height
           clientTop/Left/Width/Height
           getComputedStyle()

代碼壓縮


  • 代碼壓縮工具

    精簡代碼就是將代碼中的空格註釋去除,也有更進一步的會對變量名稱混淆精簡。根據統計精簡後文件大小會平均減小21%,即便Gzip以後文件也會減小5%

    • YUICompressor

    • Dean Edwards Packer

    • JSMin

    • GZip壓縮

      • GZip縮短在瀏覽器和服務器之間傳送數據的時間,縮短期後獲得標題是Accept-Encoding: gzip,deflate的一個文件。不過這種壓縮方法一樣也有缺點。

        • 它在服務器端和客戶端都要佔用處理器資源(以便壓縮和解壓縮)。

        • 佔用磁盤空間。

      • Gzip一般能夠減小70%網頁內容的大小,包括腳本、樣式表、圖片等任何一個文本類型的響應,包括XMLJSONGzipdeflate更高效,主流服務器都有相應的壓縮支持模塊。

      • Gzip的工做流程爲

        • 客戶端在請求Accept-Encoding中聲明能夠支持Gzip

        • 服務器將請求文檔壓縮,並在Content-Encoding中聲明該回復爲Gzip格式。

        • 客戶端收到以後按照Gzip解壓縮。

    • Closure compiler

代碼優化


  • 優化原則:

    JS與其餘語言不一樣在於它的執行效率很大程度是取決於JS engine的效率。除了引擎實現的優劣外,引擎本身也會爲一些特殊的代碼模式採起一些優化的策略。例如FFOperaSafariJAVASCRIPT引擎,都對字符串的拼接運算(+)作了特別優化。因此應該根據不一樣引擎進行不一樣優化。

    而若是作跨瀏覽器的web編程,則最大的問題是在於IE6(JScript 5.6),由於在不打hotfix的狀況下,JScript引擎的垃圾回收的bug,會致使其在真實應用中的performance跟其餘瀏覽器根本不在一個數量級上。所以在這種場合作優化,實際上就是爲JScript作優化,因此第一原則就是隻須要爲IE6(未打補丁的JScript 5.6或更早版本)作優化。

    • JS優化老是出如今大規模循環的地方:

      這倒不是說循環自己有性能問題,而是循環會迅速放大可能存在的性能問題,因此第二原則就是以大規模循環體爲最主要優化對象。

    如下的優化原則,只在大規模循環中才有意義,在循環體以外作此類優化基本上是沒有意義的。

    目前絕大多數JS引擎都是解釋執行的,而解釋執行的狀況下,在全部操做中,函數調用的效率是較低的。此外,過深的prototype繼承鏈或者多級引用也會下降效率。JScript中,10級引用的開銷大致是一次空函數調用開銷的1/2。這二者的開銷都遠遠大於簡單操做(如四則運算)。

    • 儘可能避免過多的引用層級和沒必要要的屢次方法調用:

      特別要注意的是,有些狀況下看似是屬性訪問,其實是方法調用。例如全部DOM的屬性,實際上都是方法。在遍歷一個NodeList的時候,循環 條件對於nodes.length的訪問,看似屬性讀取,其實是等價於函數調用的。並且IE DOM的實現上,childNodes.length每次是要經過內部遍歷從新計數的。(My god,可是這是真的!由於我測過,childNodes.length的訪問時間與childNodes.length的值成正比!)這很是耗費。因此 預先把nodes.length保存到js變量,固然能夠提升遍歷的性能。

    一樣是函數調用,用戶自定義函數的效率又遠遠低於語言內建函數,由於後者是對引擎本地方法的包裝,而引擎一般是c,c++,java寫的。進一步,一樣的功能,語言內建構造的開銷一般又比內建函數調用要效率高,由於前者在JS代碼的parse階段就能夠肯定和優化。

    • 儘可能使用語言自己的構造和內建函數:

      這裏有一個例子是高性能的String.format方法。 String.format傳統的實現方式是用String.replace(regex, func),在pattern包含n個佔位符(包括重複的)時,自定義函數func就被調用n次。而這個高性能實現中,每次format調用所做的只是一次Array.join而後一次String.replace(regex, string)的操做,二者都是引擎內建方法,而不會有任何自定義函數調用。兩次內建方法調用和n次的自定義方法調用,這就是性能上的差異。

    一樣是內建特性,性能上也仍是有差異的。例如在JScript中對於arguments的訪問性能就不好,幾乎遇上一次函數調用了。所以若是一個 可變參數的簡單函數成爲性能瓶頸的時候,能夠將其內部作一些改變,不要訪問arguments,而是經過對參數的顯式判斷來處理,好比:

動畫優化


  • 動畫效果在缺乏硬件加速支持的狀況下反應緩慢,例如手機客戶端。

    • 特效應該只在確實能改善用戶體驗時才使用,而不該用於炫耀或者彌補功能與可用性上的缺陷。

    • 至少要給用戶一個選擇能夠禁用動畫效果。

    • 設置動畫元素爲absolute或fixed。

      • position: staticposition: relative元素應用動畫效果會形成頻繁的reflow

      • position: absoluteposition: fixed的元素應用動畫效果只須要repaint

    • 使用一個timer完成多個元素動畫。

      • setIntervalsetTimeout是兩個經常使用的實現動畫的接口,用以間隔更新元素的風格與佈局。。

    • 動畫效果的幀率最優化的狀況是使用一個timer完成多個對象的動畫效果,其緣由在於多個timer的調用自己就會損耗必定性能。

      setInterval(function() {
        animateFirst('');
      }, 10);
      setInterval(function() {
        animateSecond('');
      }, 10);

    使用同一個timer

    setInterval(function() {
          animateFirst('');
          animateSecond('');
        }, 10);
  • 以腳本爲基礎的動畫,由瀏覽器控制動畫的更新頻率。

對象專題


  • 減小沒必要要的對象建立:

    • 建立對象自己對性能影響並不大,但因爲JAVASCRIPT的垃圾回收調度算法,致使隨着對象個數的增長,性能會開始嚴重降低(複雜度O(n^2))。

      • 如常見的字符串拼接問題,單純的屢次建立字符串對象其實根本不是下降性能的主要緣由,而是是在對象建立期間的無謂的垃圾回收的開銷。而Array.join的方式,不會建立中間字符串對象,所以就減小了垃圾回收的開銷。

    • 複雜的JAVASCRIPT對象,其建立時時間和空間的開銷都很大,應該儘可能考慮採用緩存。

    • 儘可能做用JSON格式來建立對象,而不是var obj=new Object()方法。前者是直接複製,然後者須要調用構造器。

  • 對象查找

    • 避免對象的嵌套查詢,由於JAVASCRIPT的解釋性,a.b.c.d.e嵌套對象,須要進行4次查詢,嵌套的對象成員會明顯影響性能。

    • 若是出現嵌套對象,能夠利用局部變量,把它放入一個臨時的地方進行查詢。

  • 對象屬性

    • 訪問對象屬性消耗性能過程(JAVASCRIPT對象存儲)。

      • 先從本地變量表找到對象

      • 而後遍歷屬性

      • 若是在當前對象屬性列表裏沒找到。

      • 繼續從prototype向上查找。

      • 且不能直接索引,只能遍歷。

        function f(obj) {

        return obj.a + 1;

        }

服務端優化


  • 避免404。

    • 更改404錯誤響應頁面能夠改進用戶體驗,可是一樣也會浪費服務器資源。

    • 指向外部JAVASCRIPT的連接出現問題並返回404代碼。

      • 這種加載會破壞並行加載。

      • 其次瀏覽器會把試圖在返回的404響應內容中找到可能有用的部分看成JavaScript代碼來執行。

  • 刪除重複的JAVASCRIPTCSS

    • 重複調用腳本缺點。

      • 增長額外的HTTP請求。

      • 屢次運算也會浪費時間。在IE和Firefox中無論腳本是否可緩存,它們都存在重複運算JAVASCRIPT的問題。

  • ETags配置Entity標籤。

    • ETags用來判斷瀏覽器緩存裏的元素是否和原來服務器上的一致。

      • last-modified date相比更靈活。

        >如某個文件在1秒內修改了10次,`ETags`能夠綜合`Inode`(文件的索引節點`inode`數),`MTime`(修改時間)和`Size`來精準的進行判斷,避開`UNIX`記錄`MTime`只能精確到秒的問題。服務器集羣使用,可取後兩個參數。使用`ETags`減小`Web`應用帶寬和負載
  • 權衡DNS查找次數

    • 減小主機名能夠節省響應時間。但同時也會減小頁面中並行下載的數量。

      • IE瀏覽器在同一時刻只能從同一域名下載兩個文件。當在一個頁面顯示多張圖片時,IE用戶的圖片下載速度就會受到影響。

  • 經過Keep-alive機制減小TCP鏈接。

  • 經過CDN減小延時。

  • 平行處理請求(參考BigPipe)。

  • 經過合併文件或者Image Sprites減小HTTP請求。

  • 減小重定向( HTTP 301和40x/50x)。

類型轉換專題


  • 把數字轉換成字符串。

    • 應用""+1,效率是最高。

      • 性能上來講:""+字符串>String()>.toString()>new String()

        • String()屬於內部函數,因此速度很快。

        • .toString()要查詢原型中的函數,因此速度略慢。

        • new String()最慢。

  • 浮點數轉換成整型。

    • 錯誤使用使用parseInt()

      • parseInt()是用於將字符串轉換成數字,而不是浮點數整型之間的轉換。

    • 應該使用Math.floor()或者Math.round()

      • Math是內部對象,因此Math.floor()其實並無多少查詢方法和調用的時間,速度是最快的。

邏輯判斷優化


  • switch語句。

    • 如有一系列複雜的if-else語句,能夠轉換成單個switch語句則能夠獲得更快的代碼,還能夠經過將case語句按照最可能的到最不可能的順序進行組織,來進一步優化。

內存專題


  • JAVASCRIPT的內存回收機制

    • 以Google的V8引擎爲例,在V8引擎中全部的JAVASCRIPT對象都是經過來進行內存分配的。當咱們在代碼中聲明變量賦值時,V8引擎就會在堆內存中分配一部分給這個變量。若是已申請的內存不足以存儲這個變量時,V8引擎就會繼續申請內存,直到的大小達到了V8引擎的內存上限爲止(默認狀況下,V8引擎的堆內存的大小上限在64位系統中爲1464MB,在32位系統中則爲732MB)。

    • 另外,V8引擎對堆內存中的JAVASCRIPT對象進行分代管理

      • 新生代。

        • 新生代即存活週期較短的JAVASCRIPT對象,如臨時變量、字符串等

      • 老生代。

        • 老生代則爲通過屢次垃圾回收仍然存活,存活週期較長的對象,如主控制器、服務器對象等。

  • 垃圾回收算法。

    • 垃圾回收算法一直是編程語言的研發中是否重要的​​一環,而V8引擎所使用的垃圾回收算法主要有如下幾種。

      • Scavange算法:經過複製的方式進行內存空間管理,主要用於新生代的內存空間;

      • Mark-Sweep算法和Mark-Compact算法:經過標記來對堆內存進行整理和回收,主要用於老生代對象的檢查和回收。

  • 對象進行回收。

    • 引用

      • 當函數執行完畢時,在函數內部所聲明的對象不必定就會被銷燬。

      • 引用(Reference)是JAVASCRIPT編程中十分重要的一個機制。

        • 是指代碼對對象的訪問這一抽象關係,它與C/C++的指針有點類似,但並不是同物。引用同時也是JAVASCRIPT引擎在進行垃圾回收中最關鍵的一個機制。

          var val = 'hello world';
          function foo() {
          return function() {

          return val;

          };
          }
          global.bar = foo();

        • 當代碼執行完畢時,對象valbar()並無被回收釋放,JAVASCRIPT代碼中,每一個變量做爲單獨一行而不作任何操做,JAVASCRIPT引擎都會認爲這是對對象的訪問行爲,存在了對對象的引用。爲了保證垃圾回收的行爲不影響程序邏輯的運行,JAVASCRIPT引擎不會把正在使用的對象進行回收。因此判斷對象是否正在使用中的標準,就是是否仍然存在對該對象引用

          • JAVASCRIPT引用是能夠進行轉移的,那麼就有可能出現某些引用被帶到了全局做用域,但事實上在業務邏輯裏已經不須要對其進行訪問了,這個時候就應該被回收,可是JAVASCRIPT引擎仍會認爲程序仍然須要它。

  • IE下閉包引發跨頁面內存泄露。

  • JAVASCRIPT的內存泄露處理

    • DOM對象添加的屬性是一個對象的引用。

      var MyObject = {};
      document.getElementByIdx_x('myDiv').myProp = MyObject;

      解決方法:在window.onunload事件中寫上:

      document.getElementByIdx_x('myDiv').myProp = null;

    • DOM對象與JS對象相互引用。

      function Encapsulator(element) {

      this.elementReference = element;
      element.myProp = this;

      }
      new Encapsulator(document.getElementByIdx_x('myDiv'));

      解決方法:在onunload事件中寫上:

      document.getElementByIdx_x('myDiv').myProp = null;

    • 給DOM對象用attachEvent綁定事件。

      function doClick() {}
      element.attachEvent("onclick", doClick);

      解決方法:在onunload事件中寫上:

      element.detachEvent('onclick', doClick);

    • 從外到內執行appendChild。這時即便調用removeChild也沒法釋放。

      var parentDiv = document.createElement_x("div");
      var childDiv = document.createElement_x("div");
      document.body.appendChild(parentDiv);
      parentDiv.appendChild(childDiv);

      解決方法:從內到外執行appendChild:

      var parentDiv = document.createElement_x("div");
      var childDiv = document.createElement_x("div");
      parentDiv.appendChild(childDiv);
      document.body.appendChild(parentDiv);

    • 反覆重寫同一個屬性會形成內存大量佔用(但關閉IE後內存會被釋放)。

      for(i = 0; i < 5000; i++) {

      hostElement.text = "asdfasdfasdf";

      }

      這種方式至關於定義了5000個屬性,解決方法:無。
  • 內存不是緩存

    • 不要輕易將內存看成緩存使用。

    • 若是是很重要的資源,請不要直接放在內存中,或者制定過時機制,自動銷燬過時緩存

  • CollectGarbage

    • CollectGarbageIE的一個特有屬性,用於釋放內存的使用方法,將該變量或引用對象設置爲nulldelete而後在進行釋放動做,在作CollectGarbage前,要必需清楚的兩個必備條件:(引用)。

      • 一個對象在其生存的上下文環境以外,即會失效。

      • 一個全局的對象在沒有被執用(引用)的狀況下,即會失效

  • 事件優化


    • 使用事件代理

      • 當存在多個元素須要註冊事件時,在每一個元素上綁定事件自己就會對性能有必定損耗。

      • 因爲DOM Level2事件模 型中全部事件默認會傳播到上層文檔對象,能夠藉助這個機制在上層元素註冊一個統一事件對不一樣子元素進行相應處理。

    捕獲型事件先發生。兩種事件流會觸發DOM中的全部對象,從document對象開始,也在document對象結束。

    <ul id="parent-list">
            <li id="post-1">Item 1
            <li id="post-2">Item 2
            <li id="post-3">Item 3
            <li id="post-4">Item 4
            <li id="post-5">Item 5
            <li id="post-6">Item 6
        </li></ul>
        // Get the element, add a click listener...
        document.getElementById("parent-list").addEventListener("click",function(e) {
            // e.target is the clicked element!
            // If it was a list item
            if(e.target && e.target.nodeName == "LI") {
                // List item found!  Output the ID!
                console.log("List item ",e.target.id.replace("post-")," was clicked!");
            }
        });

    數組專題


    • 當須要使用數組時,可以使用JSON格式的語法

      • 即直接使用以下語法定義數組:[parrm,param,param...],而不是採用new Array(parrm,param,param...)這種語法。使用JSON格式的語法是引擎直接解釋。然後者則須要調用Array的構造器。

    • 若是須要遍歷數組,應該先緩存數組長度,將數組長度放入局部變量中,避免屢次查詢數組長度。

      • 根據字符串、數組的長度進行循環,而一般這個長度是不變的,好比每次查詢a.length,就要額外進行一個操做,而預先把var len=a.length,則每次循環就少了一次查詢。

    同域跨域


    • 避免跳轉

      • 同域:注意避免反斜槓 「/」 的跳轉;

      • 跨域:使用Alias或者mod_rewirte創建CNAME(保存域名與域名之間關係的DNS記錄)

    性能測試工具


    • js性能優化和內存泄露問題及檢測分析工具

      • 性能優化ajax工具diviefirebug

      • [web性能分析工具YSlow]

        • performance性能評估打分,右擊箭頭可看到改進建議。

        • stats緩存狀態分析,傳輸內容分析。

        • components全部加載內容分析,能夠查看傳輸速度,找出頁面訪問慢的瓶頸。

        • tools能夠查看js和css,並打印頁面評估報告。

      • 內存泄露檢測工具sIEve

        • sIEve是基於IE的內存泄露檢測工具,須要下載運行,能夠查看dom孤立節點和內存泄露及內存使用狀況。

          1. 列出當前頁面內全部dom節點的基本信息(html id style 等)

          2. 頁面內全部dom節點的高級信息 (內存佔用,數量,節點的引用)

          3. 能夠查找出頁面中的孤立節點

          4. 能夠查找出頁面中的循環引用

          5. 能夠查找出頁面中產生內存泄露的節點

      • 內存泄露提示工具leak monitor

        • leak monitor在安裝後,當離開一個頁面時,好比關閉窗口,若是頁面有內存泄露,會彈出一個文本框進行即時提示。

      • 代碼壓縮工具

        • YUI壓縮工具

        • Dean Edwards Packer

        • JSMin

      • Blink/Webkit瀏覽器

        • Blink/Webkit瀏覽器中(Chrome, Safari, Opera),咱們能夠藉助其中的Developer ToolsProfiles工具來對咱們的程序進行內存檢查。

          Developer Tools - Profiles
    • Node.js中的內存檢查

      • Node.js中,咱們可使用node-heapdumpnode-memwatch模塊進​​行內存檢查。

        var heapdump = require('heapdump');
        var fs = require('fs');
        var path = require('path');
        fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid);

      在業務代碼中引入node-heapdump以後,咱們須要在某個運行時期,向Node.js進程發送SIGUSR2信號,讓node-heapdump抓拍一份堆內存的快照。

      $ kill -USR2 (cat app.pid)
      
      這樣在文件目錄下會有一個以`heapdump-<sec>.<usec>.heapsnapshot`格式命名的快照文件,咱們可使用瀏覽器的`Developer Tools`中的`Profiles`工具將其打開,並進行檢查。
    • 分析瀏覽器提供的Waterfall圖片來思考優化入口。

    • 新的測試手段(Navigation, Resource, 和User timing。

    循環專題


    • 循環是一種經常使用的流程控制。

      • JAVASCRIPT提供了三種循環。

        • for(;;)

          • 推薦使用for循環,若是循環變量遞增或遞減,不要單獨對循環變量賦值,而應該使用嵌套的++–-運算符。

          • 代碼的可讀性對於for循環的優化。

          • -=1

          • 從大到小的方式循環(這樣缺點是下降代碼的可讀性)。

            /效率低/
            var divs = document.getElementsByTagName("div");
            for(var i = 0; i < divs.length; i++){

            ...

            }
            /效率高,適用於獲取DOM集合,若是純數組則兩種狀況區別不到/
            var divs = document.getElementsByTagName("div");
            for(var i = 0, len = divs.length; i < len; i++){

            ...

            }
            /IE6.0下,for(;;)循環在執行中,第一種狀況會每次都計算一下長度,而第二種狀況倒是在開始的時候計算長度,並把其保存到一個變量中,因此其執行效率要高點,因此在咱們使用for(;;)循環的時候,特別是須要計算長度的狀況,咱們應該開始將其保存到一個變量中。/

        • while()

          • for(;;)while()循環的性能基本持平。

        • for(in)

          • 在這三種循環中for(in)內部實現是構造一個全部元素的列表,包括array繼承的屬性,而後再開始循環,而且須要查詢hasOwnProperty。因此for(in)相對for(;;)循環性能要慢。

    • 選擇正確的方法

      • 避免沒必要要的屬性查找。

        • 訪問變量數組O(1)操做。

        • 訪問對象上的屬性是一個O(n)操做。

          對象上的任何屬性查找都要比訪問變量或數組花費更長時間,由於必須在原型鏈中對擁有該名稱的屬性進行一次搜索,即屬性查找越多,執行時間越長。因此針對須要屢次用到對象屬性,應將其存儲在局部變量。
      • 優化循環。

        • 減值迭代。

          • 大多數循環使用一個從0開始,增長到某個特定值的迭代器。在不少狀況下,從最大值開始,在循環中不斷減值的迭代器更加有效。

        • 簡化終止條件。

          • 因爲每次循環過程都會計算終止條件,故必須保證它儘量快,即避免屬性查找或其它O(n)的操做。

        • 簡化循環體。

          • 循環體是執行最多的,故要確保其被最大限度地優化。確保沒有某些能夠被很容易移出循環的密集計算。

        • 使用後測試循環。

          • 最經常使用的for和while循環都是前測試循環,而如do-while循環能夠避免最初終止條件的計算,因些計算更快。

        for(var i = 0; i < values.length; i++) {
                    process(values[i]);
                }

        優化1:簡化終止條件

        for(var i = 0, len = values.length; i < len; i++) {
            process(values[i]);
        }

        優化2:使用後測試循環(注意:使用後測試循環須要確保要處理的值至少有一個)

      • 展開循環。

        • 當循環的次數肯定時,消除循環並使用屢次函數調用每每更快。

        • 當循環的次數不肯定時,可使用Duff裝置來優化。

          • Duff裝置的基本概念是經過計算迭代的次數是否爲8的倍數將一個循環展開爲一系列語句。

        // Jeff Greenberg for JS implementation of Duff's Device
        // 假設:values.length 0
        function process(v) {
            alert(v);
        }
        var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
        var iterations = Math.ceil(values.length / 8);
        var startAt = values.length % 8;
        var i = 0; 
        do {
            switch(startAt) {
                case 0 : process(values[i++]);
                case 7 : process(values[i++]);
                case 6 : process(values[i++]);
                case 5 : process(values[i++]);
                case 4 : process(values[i++]);
                case 3 : process(values[i++]);
                case 2 : process(values[i++]);
                case 1 : process(values[i++]);
            }
            startAt = 0;
        }while(--iterations 0);

        如上展開循環能夠提高大數據集的處理速度。接下來給出更快的Duff裝置技術,將do-while循環分紅2個單獨的循環。(注:這種方法幾乎比原始的Duff裝置實現快上40%。)

        // Speed Up Your Site(New Riders, 2003)
        function process(v) {
            alert(v);
        }     
        var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
        var iterations = Math.floor(values.length / 8);
        var leftover = values.length % 8;
        var i = 0; 
        if(leftover 0) {
            do {
                process(values[i++]);
            }while(--leftover 0);
        }     
        do {
            process(values[i++]);
            process(values[i++]);
            process(values[i++]);
            process(values[i++]);
            process(values[i++]);
            process(values[i++]);
            process(values[i++]);
            process(values[i++]);
        }while(--iterations 0);

        針對大數據集使用展開循環能夠節省不少時間,但對於小數據集,額外的開銷則可能得不償失。

    • 避免在循環中使用try-catch

      • try-catch-finally語句在catch語句被執行的過程當中會動態構造變量插入到當前域中,對性能有必定影響。

      • 若是須要異常處理機制,能夠將其放在循環外層使用。

        • 循環中使用try-catch

          for ( var i = 0; i < 200; i++) {
          try {} catch (e) {}
          }

        • 循環外使用try-catch

          try {
                for ( var i = 0; i < 200; i++) {}
              } catch (e) {}
  • 避免遍歷大量元素:

    • 避免對全局DOM元素進行遍歷,若是parent已知能夠指定parent在特定範圍查詢。

      var elements = document.getElementsByTagName( '*' );
      for (i = 0; i < elements.length; i++) {
      if (elements[i].hasAttribute( 'selected' )) {}
      }

      若是已知元素存在於一個較小的範圍內,

      var elements = document.getElementById( 'canvas' ).getElementsByTagName ( '*' );
      for (i = 0; i < elements.length; i++) {
      if (elements[i].hasAttribute( 'selected' )) {}
      }

  • 原型優化


    • 經過原型優化方法定義。

      • 若是一個方法類型將被頻繁構造,經過方法原型從外面定義附加方法,從而避免方法的重複定義。

      • 能夠經過外部原型的構造方式初始化值類型的變量定義。(這裏強調值類型的緣由是,引用類型若是在原型中定義,一個實例對引用類型的更改會影響到其餘實例。)

        • 這條規則中涉及到JAVASCRIPT中原型的概念,構造函數都有一個prototype屬性,指向另外一個對象。這個對象的全部屬性和方法,都會被構造函數的實例繼承。能夠把那些不變的屬性和方法,直接定義在prototype對象上。

          • 能夠經過對象實例訪問保存在原型中的值。

          • 不能經過對象實例重寫原型中的值。

          • 在實例中添加一個與實例原型同名屬性,那該屬性就會屏蔽原型中的屬性。

          • 經過delete操做符能夠刪除實例中的屬性。

    運算符專題


    • 使用運算符時,儘可能使用+=-=*=\=等運算符號,而不是直接進行賦值運算。

    • 位運算

      • 當進行數學運算時位運算較快,位運算操做要比任何布爾運算算數運算快,如取模邏輯與邏輯或也能夠考慮用位運算來替換。

    重繪專題


    • 減小頁面的重繪

      • 減小頁面重繪雖然本質不是JAVASCRIPT優化,但重繪每每是由JAVASCRIPT引發的,而重繪的狀況直接影響頁面性能。

        var str = "<div>這是一個測試字符串</div>";
        /效率低/
        var obj = document.getElementsByTagName("body");
        for(var i = 0; i < 100; i++){

        obj.innerHTML += str + i;

        }
        /效率高/
        var obj = document.getElementsByTagName("body");
        var arr = [];
        for(var i = 0; i < 100; i++){

        arr[i] = str + i;

        }
        obj.innerHTML = arr.join("");

      通常影響頁面重繪的不只僅是innerHTML,若是改變元素的樣式,位置等狀況都會觸發頁面重繪,因此在平時必定要注意這點。

    • 使用HTML5和CSS3的一些新特性。

    • 避免在HTML裏面縮放圖片。

    • 避免使用插件。

    • 確保使用正確的字體大小。

    • 決定當前頁面是否是能被訪問。

    字符串專題


    • 對字符串進行循環操做。

      • 替換、查找等操做,使用正則表達式。

        • 由於JAVASCRIPT的循環速度較慢,而正則表達式的操做是用C寫成的API,性能比較好。

    • 字符串的拼接。

      • 字符串的拼接在咱們開發中會常常遇到,因此我把其放在首位,咱們每每習慣的直接用+=的方式來拼接字符串,其實這種拼接的方式效率很是的低,咱們能夠用一種巧妙的方法來實現字符串的拼接,那就是利用數組的join方法,具體請看我整理的:Web前端開發規範文檔中的javaScript書寫規範倒數第三條目。

      • 不過也有另外一種說法,一般認爲須要用Array.join的方式,可是因爲SpiderMonkey等引擎對字符串的「+」運算作了優化,結果使用Array.join的效率反而不如直接用「+」,可是若是考慮IE6,則其餘瀏覽器上的這種效率的差異根本不值一提。具體怎麼取捨,諸君自定。

    做用域鏈和閉包優化


    • 做用域。

      • 做用域(scope)是JAVASCRIPT編程中一個重要的運行機制,在JAVASCRIPT同步和異步編程以及JAVASCRIPT內存管理中起着相當重要的做用。

      • JAVASCRIPT中,能造成做用域的有以下幾點。

        • 函數的調用

        • with語句

          • with會建立自已的做用域,所以會增長其中執行代碼的做用域的長度。

        • 全局做用域。

          如下代碼爲例:

          var foo = function() {
          var local = {};
          };
          foo();
          console.log(local); //=undefined

          var bar = function() {
          local = {};
          };
          bar();
          console.log(local); //={}

        /這裏咱們定義了foo()函數和bar()函數,他們的意圖都是爲了定義一個名爲local的變量。在foo()函數中,咱們使用var語句來聲明定義了一個local變量,而由於函數體內部會造成一個做用域,因此這個變量便被定義到該做用域中。並且foo()函數體內並無作任何做用域延伸的處理,因此在該函數執行完畢後,這個local變量也隨之被銷燬。而在外層做用域中則沒法訪問到該變量。而在bar()函數內,local變量並無使用var語句進行聲明,取而代之的是直接把local做爲全局變量來定義。故外層做用域能夠訪問到這個變量。/

        local = {};
        // 這裏的定義等效於
        global.local = {};
    • 做用域鏈

      • JAVASCRIPT編程中,會遇到多層函數嵌套的場景,這就是典型的做用域鏈的表示。

        function foo() {
        var val = 'hello';
        function bar() {

        function baz() {
          global.val = 'world;'
        };
        baz();
        console.log(val); //=hello

        };
        bar();
        };
        foo();

        /**在`JAVASCRIPT`中,變量標識符的查找是從當前做用域開始向外查找,直到全局做用域爲止。因此`JAVASCRIPT`代碼中對變量的訪問只能向外進行,而不能逆而行之。baz()函數的執行在全局做用域中定義了一個全局變量val。而在bar()函數中,對val這一標識符進行訪問時,按照從內到外的查找原則:在bar函數的做用域中沒有找到,便到上一層,即foo()函數的做用域中查找。然而,使你們產生疑惑的關鍵就在這裏:本次標識符訪問在foo()函數的做用域中找到了符合的變量,便不會繼續向外查找,故在baz()函數中定義的全局變量val並無在本次變量訪問中產生影響。**/
    • 減小做用域鏈上的查找次數

      • JAVASCRIPT代碼在執行的時候,若是須要訪問一個變量或者一個函數的時候,它須要遍歷當前執行環境的做用域鏈,而遍歷是從這個做用域鏈的前端一級一級的向後遍歷,直到全局執行環境。

        /效率低/
        for(var i = 0; i < 10000; i++){

        var but1 = document.getElementById("but1");

        }
        /效率高/
        /避免全局查找/
        var doc = document;
        for(var i = 0; i < 10000; i++){

        var but1 = doc.getElementById("but1");

        }
        /上面代碼中,第二種狀況是先把全局對象的變量放到函數裏面先保存下來,而後直接訪問這個變量,而第一種狀況是每次都遍歷做用域鏈,直到全局環境,咱們看到第二種狀況實際上只遍歷了一次,而第一種狀況倒是每次都遍歷了,並且這種差異在多級做用域鏈和多個全局變量的狀況下還會表現的很是明顯。在做用域鏈查找的次數是O(n)。經過建立一個指向document的局部變量,就能夠經過限制一次全局查找來改進這個函數的性能。/

    • 閉包

      • JAVASCRIPT中的標識符查找遵循從內到外的原則。

        function foo() {
              var local = 'Hello';
              return function() {
                return local;
              };
            }
            var bar = foo();
            console.log(bar()); //=Hello
        
            /**這裏所展現的讓外層做用域訪問內層做用域的技術即是閉包(Closure)。得益於高階函數的應用,使foo()函數的做用域獲得`延伸`。foo()函數返回了一個匿名函數,該函數存在於foo()函數的做用域內,因此能夠訪問到foo()函數做用域內的local變量,並保存其引用。而因這個函數直接返回了local變量,因此在外層做用域中即可直接執行bar()函數以得到local變量。**/
        • 閉包是JAVASCRIPT的高級特性,由於把帶有​​內部變量引用的函數帶出了函數外部,因此該做用域內的變量在函數執行完畢後的並不必定會被銷燬,直到內部變量的引用被所有解除。因此閉包的應用很容易形成內存沒法釋放的狀況。

        • 良好的閉包管理。

          • 循環事件綁定、私有屬性、含參回調等必定要使用閉包時,並謹慎對待其中的細節。

            • 循環綁定事件,咱們假設一個場景:有六個按鈕,分別對應六種事件,當用戶點擊按鈕時,在指定的地方輸出相應的事件。

              var btns = document.querySelectorAll('.btn'); // 6 elements
              var output = document.querySelector('#output');
              var events = [1, 2, 3, 4, 5, 6];
              // Case 1
              for (var i = 0; i < btns.length; i++) {
              btns[i].onclick = function(evt) {

              output.innerText += 'Clicked ' + events[i];

              };
              }
              /這裏第一個解決方案顯然是典型的循環綁定事件錯誤,這裏不細說,詳細能夠參照我給一個網友的回答;而第二和第三個方案的區別就在於閉包傳入的參數。/
              // Case 2
              for (var i = 0; i < btns.length; i++) {
              btns[i].onclick = (function(index) {

              return function(evt) {
                output.innerText += 'Clicked ' + events[index];
              };

              })(i);
              }
              /第二個方案傳入的參數是當前循環下標,然後者是直接傳入相應的事件對象。事實上,後者更適合在大量數據應用的時候,由於在JavaScript的函數式編程中,函數調用時傳入的參數是基本類型對象,那麼在函數體內獲得的形參會是一個複製值,這樣這個值就被看成一個局部變量定義在函數體的做用域內,在完成事件綁定以後就能夠對events變量進行手工解除引用,以減輕外層做用域中的內存佔用了。並且當某個元素被刪除時,相應的事件監聽函數、事件對象、閉包函數也隨之被銷燬回收。/
              // Case 3
              for (var i = 0; i < btns.length; i++) {
              btns[i].onclick = (function(event) {

              return function(evt) {
                output.innerText += 'Clicked ' + event;
              };

              })(events[i]);
              }

    • 避開閉包陷阱

      • 閉包是個強大的工具,但同時也是性能問題的主要誘因之一。不合理的使用閉包會致使內存泄漏。

      • 閉包的性能不如使用內部方法,更不如重用外部方法。

        • 因爲IE 9瀏覽器的DOM節點做爲COM對象來實現,COM內存管理是經過引用計數的方式,引用計數有個難題就是循環引用,一旦DOM引用了閉包(例如event handler),閉包的上層元素又引用了這個DOM,就會形成循環引用從而致使內存泄漏。

    • 善用函數

      • 使用一個匿名函數在代碼的最外層進行包裹。

        ;(function() {
           // 主業務代碼
         })();

    有的甚至更高級一點:

    ;(function(win, doc, $, undefined) {
          // 主業務代碼
        })(window, document, jQuery);

    甚至連如RequireJS, SeaJS, OzJS 等前端模塊化加載解決方案,都是採用相似的形式:

    /**RequireJS**/
        define(['jquery'], function($) {
          // 主業務代碼
        });
        /**SeaJS**/
        define('m​​odule', ['dep', 'underscore'], function($, _) {
          // 主業務代碼
        });

    被定義在全局做用域的對象,多是會一直存活到進程退出的,若是是一個很大的對象,那就麻煩了。好比有的人喜歡在JavaScript中作模版渲染:

    <?php
          $db = mysqli_connect(server, user, password, 'myapp');
          $topics = mysqli_query($db, "SELECT * FROM topics;");
        ?>
        <!doctype html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <title>你是猴子請來的逗比麼?</title>
        </head>
        <body>
          <ul id="topics"></ul>
          <script type="text/tmpl" id="topic-tmpl">
            <li class="topic">
              <h1><%=title%></h1>
              <p><%=content%></p>
            </li>
          </script>
          <script type="text/javascript">
            var data = <?php echo json_encode($topics); ?>;
            var topicTmpl = document.querySelector('#topic-tmpl').innerHTML;
            var render = function(tmlp, view) {
              var complied = tmlp
                .replace(/\n/g, '\\n')
                .replace(/<%=([\s\S]+?)%>/g, function(match, code) {
                  return '" + escape(' + code + ') + "';
                });
              complied = [
                'var res = "";',
                'with (view || {}) {',
                  'res = "' + complied + '";',
                '}',
                'return res;'
              ].join('\n');
              var fn = new Function('view', complied);
              return fn(view);
            };
            var topics = document.querySelector('#topics');
            function init()
              data.forEach(function(topic) {
                topics.innerHTML += render(topicTmpl, topic);
              });
            }
            init();
          </script>
        </body>
        </html>

    在從數據庫中獲取到的數據的量是很是大的話,前端完成模板渲染之後,data變量便被閒置在一邊。可由於這個變量是被定義在全局做用域中的,因此JAVASCRIPT引擎不會將其回收銷燬。如此該變量就會一直存在於老生代堆內存中,直到頁面被關閉。但是若是咱們做出一些很簡單的修改,在邏輯代碼外包裝一層函數,這樣效果就大不一樣了。當UI渲染完成以後,代碼對data的引用也就隨之解除,而在最外層函數執行完畢時,JAVASCRIPT引擎就開始對其中的對象進行檢查,data也就能夠隨之被回收。

    GITHUB:前端性能優化指南

    參考和借鑑了你們的經驗,收集整理了這一篇開發規範,感謝全部的原做者,衆人拾柴火焰高,技術無國界,持續更新中。

    相關文章
    相關標籤/搜索