javascript效能優化

小婉發表於2019-06-10

javascript效能優化

載入和執行

1、 </body>閉合標籤之前,將所有的<script> 標籤放在頁面底部,確保在腳步執行之前頁面已經完成渲染。

2、 合併指令碼。下載單個 100KB 的檔案將比下載 4 個 25KB 的檔案更快,因此頁面標籤的<script>標籤越少,載入也就越快,響應也越迅速。無論外鏈檔案或者內嵌指令碼。

3、 使用無阻塞下載 javascript 的方法,即在頁面載入完成後才載入 Javascript 程式碼,以下有幾種無阻塞下載方法:

  • 使用<script>defer 屬性(只有 IE4+和 FireFox3.5+支援)

    <script src="file.js" defer></script>
    複製程式碼
  • 使用動態建立的<script>元素來下載並執行程式碼

    // 通過監聽script元素接收完成事件來獲得指令碼載入完成時的狀態
    // 封裝標準和IE特有的實現方法函式
    function loadscript(url, callback) {
      let script = document.createElement('script');
      if (script.readyState) {
        //IE
        script.onreadystatechange = function() {
          if (script.readyState === 'loaded' || script.readyState === 'complete') {
            script.onreadystatechange = null;
            callback();
          }
        };
      } else {
        // 其他瀏覽器
        scripy.onload = function() {
          callback();
        };
      }
      script.src = url;
      document.getElementsByTagName('head')[0].appendchild(script);
    }
    // 使用上述封裝函式載入檔案
    loadscript('file.js', function() {
      console.log('file is loaded ~');
    });
    複製程式碼
  • 使用 XHR 物件下載 javascript程式碼並注入頁面中

    let xhr = new XMLHttpRequest();
    xhr.open('get', 'file.js', true);
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
          let script = document.createElement('script');
          script.text = xhr.responseText;
          document.body.appendChild(script);
        }
      }
    };
    複製程式碼

總結:

向頁面中新增大量 JavaScript 的推薦做法:先新增動態載入所需程式碼,然後載入初始化頁面所需剩下程式碼

資料儲存

1、訪問字面量和區域性變數的速度比訪問陣列元素和物件成員快,因此儘量使用字面量和區域性變數,減少陣列項和物件成員的使用。

2、儘可能使用區域性變數。如果某個跨作用域的值在函式中被引用一次以上,把它儲存到區域性變數中。

// 不推薦
function initFn() {
  // document.getElementById('myImg')多次被引用
  document.getElementById('myImg').src = 'photo.png';
  document.getElementById('myImg').alt = 'photo';
}

// 推薦
function initFn() {
  // 改寫
  let myImg = document.getElementById('myImg');
  myImg.src = 'photo.png';
  myImg.alt = 'photo';
}
複製程式碼

3、避免使用 with 語句,它會改變執行環境作用域鏈。try-catch 中的 catch 也有同樣的影響,看實際情況中進行運用。

4、屬性和方法在原型鏈中的位置越深,訪問的速度越慢

總結:

把常用的物件成員、陣列元素、跨域變數儲存在區域性變數中

DOM

1、最小化 DOM 訪問次數,如果需要多次訪問某個 DOM 節點,使用區域性變數儲存它的引用。

// 不推薦
function innerHTMLLoop(){
  for(let i = 0; i < 5; i++){
    document.getElementById('myDiv').innerHTML += 'a';
  }
}

// 推薦
function innerHTMLLoop(){
  let content = '';
  for(let i = 0; i < 5; i++){
    content += 'a';
  }
  document.getElementById('myDiv').innerHTML = content;
}
複製程式碼

2、如果需要在迭代中使用 HTML 集合,把它的長度快取到一個變數中;如果需要多次操作集合,把它拷貝到一個陣列中。

  • HTML 集合有document.getElementsByName(),document.getElementsClassName(),document.getElementsByTagName(),document.images,document.links,document.forms等等,返回的是類似陣列的列表,但是它以一種“假定實時態”實時存在,即當底層文件物件更新時,它也會自動更新。

    // 死迴圈
    let divs = document.getElementsByTagName('div');
    for (let i = 0; i < divs.length; i++) {
      document.body.appendChild(document.createElement('div'));
    }
    複製程式碼
  • 當遍歷一個集合時,把集合儲存在區域性變數中,並把 length 快取在迴圈外部

    // 推薦
    function collectNodes() {
      let coll = document.getElementsByTagName('div');
      let len = coll.length;
      let nodeName = '';
      let tagName = '';
      let el = null;
      for (let i = 0; i < len; i++) {
        el = coll[i];
        nodeName = el.nodeName;
        tagName = el.tagName;
      }
      return { nodeName, tagName };
    }
    複製程式碼

3、減少重繪和重排,批量修改樣式時,“離線”操作 DOM 樹,使用快取,並減少訪問佈局資訊的次數。

  • 當頁面佈局和幾何屬性改變時需要“重排”。下列情況均會發生“重排”:

    1、新增或刪除可見的 DOM 元素

    2、元素位置發生改變

    3、元素尺寸改變(包括:外邊距、內邊距、邊框厚度、寬度、高度等屬性改變)

    4、內容改變,如文字改變或圖片被另一個不同尺寸的圖片代替

    5、頁面渲染器初始化

    6、瀏覽器視窗改變

  • 完成“重排”後,瀏覽器會重新繪製受影響的部分到螢幕中,過程為“重繪”。

  • 減少“重繪”和“重排”

    1、使元素脫離文件流

    2、對其應用多次改變

    3、把元素帶回文件中

    // 第一種方式:隱藏元素,應用修改,重新顯示
    let ul = document.getElementById('nyList');
    ul.style.display = 'none';
    // 向ul中新增附加資料
    appendDataToElement(ul, data);
    ul.style.display = 'block';
    
    // 第二種方式:使用文件片段(document fragment)在當前DOM之外構建一個子樹,再把它拷貝到文件(推薦)
    let fragment = document.createDocumentFragment();
    // 向fragment中新增附加資料
    appendDataToElement(ul, data);
    document.getElementById('myList').appendChild(fragment);
    
    // 第三種方式:將原始元素拷貝到一個脫離文件的節點中,修改副本,完成後再替換原始元素
    let old = document.getElementById('myList');
    let clone = old.cloneNode(true);
    // 向clone中新增附加資料
    appendDataToElement(ul, data);
    old.parentNode.replaceChild(clone, old);
    複製程式碼

4、使用事件委託減少事件處理器的數量

// Event物件提供了一個屬性叫target,可以返回事件的目標節點,我們成為事件源,也就是說,target就可以表示為當前的事件操作的dom,但是不是真正操作dom,當然,這個是有相容性的,標準瀏覽器用ev.target,IE瀏覽器用event.srcElement。
myLi.onclick = function(e) {
  let e = e || window.event;
  let target = ev.target || ev.srcElement;
  if (target.nodeName.toLowerCase() == 'li') {
    alert(target.innerHTML);
  }
};
複製程式碼

演算法和流程控制

  • 選擇時間複雜度低的演算法
  • 避免使用 for-in 迴圈,除非需要遍歷一個屬性數量未知的物件
  • 使用 if-else 要確保最可能出現的條件放在首位,條件數量大時,優先使用 switch
  • 使用查詢表。查詢表相對於 if-else 和 switch,不近速度快,而且可讀性好
    // 將返回值集合存入陣列
    let results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,];
    // 返回當前結果
    return results[value];
    複製程式碼

構建和部署

  • 合併 JavaScript 檔案以減少 HTTP 請求數

  • 使用 webpack 壓縮 JavaScript 檔案

  • 使用 CDN 提供 JavaScript 檔案;CDN 不僅可以提升效能,也會為你管理檔案的壓縮和快取

參考:圖靈圖書《高效能JavaScript》

相關文章