javascript性能優化

本文主要是在我讀《高性能Javascript》以後,想要記錄下一些有用的優化方案,而且就我自己的一些經驗,來你們一塊兒分享下javascript

Javascript的加載與執行

你們都知道,瀏覽器在解析DOM樹的時候,當解析到script標籤的時候,會阻塞其餘的全部任務,直到該js文件下載、解析執行完成後,纔會繼續往下執行。所以,這個時候瀏覽器就會被阻塞在這裏,若是將script標籤放在head裏的話,那麼在該js文件加載執行前,用戶只能看到空白的頁面,這樣的用戶體驗確定是特別爛。對此,經常使用的方法有如下:css

  • 將全部的script標籤都放到body最底部,這樣能夠保證js文件是最後加載並執行的,能夠先將頁面展示給用戶。可是,你首先得清楚,頁面的首屏渲染是否依賴於你的部分js文件,若是是的話,則須要將這一部分js文件放到head上。
  • 使用defer,好比下面這種寫法。使用defer這種寫法時,雖然瀏覽器解析到該標籤的時候,也會下載對應的js文件,不過它並不會立刻執行,而是會等到DOM解析完後(DomContentLoader以前)纔會執行這些js文件。所以,就不會阻塞到瀏覽器。
<script src="test.js" type="text/javascript" defer></script>
  • 動態加載js文件,經過這種方式,能夠在頁面加載完成後,再去加載所須要的代碼,也能夠經過這種方式實現js文件懶加載/按需加載,好比如今比較常見的,就是webpack結合vue-router/react-router實現按需加載,只有訪問到具體路由的時候,才加載相應的代碼。具體的方法以下:

1.動態的插入script標籤來加載腳本,好比經過如下代碼vue

function loadScript(url, callback) {
    const script = document.createElement('script');
    script.type = 'text/javascript';
    // 處理IE
    if (script.readyState) {
      script.onreadystatechange = function () {
        if (script.readyState === 'loaded' || script.readyState === 'complete') {
          script.onreadystatechange = null;
          callback();
        }
      }
    } else {
      // 處理其餘瀏覽器的狀況
      script.onload = function () {
        callback();
      }
    }
    script.src = url;
    document.body.append(script);
  }

  // 動態加載js
  loadScript('file.js', function () {
    console.log('加載完成');
  })

2.經過xhr方式加載js文件,不過經過這種方式的話,就可能會面臨着跨域的問題。例子以下:java

const xhr = new XMLHttpRequest();
  xhr.open('get', 'file.js');
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        const script = document.createElement('script');
        script.type = 'text/javascript';
        script.text = xhr.responseText;
        document.body.append(script);
      }
    }
  }

3.將多個js文件合併爲同一個,而且進行壓縮。 緣由:目前瀏覽器大多已經支持並行下載js文件了,可是併發下載仍是有必定的數量限制了(基於瀏覽器,一部分瀏覽器只能下載4個),而且,每個js文件都須要創建一次額外的http鏈接,加載4個25KB的文件比起加載一個100KB的文件消耗的時間要大。所以,咱們最好就是將多個js文件合併爲同一個,而且進行代碼壓縮。node

javascript做用域

當一個函數執行的時候,會生成一個執行上下文,這個執行上下文定義了函數執行時的環境。當函數執行完畢後,這個執行上下文就會被銷燬。所以,屢次調用同一個函數會致使建立多個執行上下文。每隔執行上下文都有本身的做用域鏈。相信你們應該早就知道了做用域這個東西,對於一個函數而言,其第一個做用域就是它函數內部的變量。在函數執行過程當中,每遇到一個變量,都會搜索函數的做用域鏈找到第一個匹配的變量,首先查找函數內部的變量,以後再沿着做用域鏈逐層尋找。所以,若咱們要訪問最外層的變量(全局變量),則相比直接訪問內部的變量而言,會帶來比較大的性能損耗。所以,咱們能夠將常用的全局變量引用儲存在一個局部變量裏react

const a = 5;
function outter () {
  const a = 2;
  function inner () {
    const b = 2;
    console.log(b); // 2
    console.log(a); // 2
  }
  inner();
}

對象的讀取

javascript中,主要分爲字面量、局部變量、數組元素和對象這四種。訪問字面量和局部變量的速度最快,而訪問數組元素和對象成員相對較慢。而訪問對象成員的時候,就和做用域鏈同樣,是在原型鏈(prototype)上進行查找。所以,若查找的成員在原型鏈位置太深,則訪問速度越慢。所以,咱們應該儘量的減小對象成員的查找次數和嵌套深度。好比如下代碼webpack

// 進行兩次對象成員查找
  function hasEitherClass(element, className1, className2) {
    return element.className === className1 || element.className === className2;
  }
  // 優化,若是該變量不會改變,則可使用局部變量保存查找的內容
  function hasEitherClass(element, className1, className2) {
    const currentClassName = element.className;
    return currentClassName === className1 || currentClassName === className2;
  }

DOM操做優化

  • 最小化DOM的操做次數,儘量的用javascript來處理,而且儘量的使用局部變量儲存DOM節點。好比如下的代碼:
// 優化前,在每次循環的時候,都要獲取id爲t的節點,而且設置它的innerHTML
  function innerHTMLLoop () {
    for (let count = 0; count < 15000; count++) {
      document.getElementById('t').innerHTML += 'a';
    }
  }
  // 優化後,
  function innerHTMLLoop () {
    const tNode = document.getElemenById('t');
    const insertHtml = '';
    for (let count = 0; count < 15000; count++) {
      insertHtml += 'a';
    }
    tNode.innerHtml += insertHtml;
  }
  • 儘量的減小重排和重繪,重排和重匯可能會代價很是昂貴,所以,爲了減小重排重匯的發生次數,咱們能夠作如下的優化

1.當咱們要對Dom的樣式進行修改的時候,咱們應該儘量的合併全部的修改而且一次處理,減小重排和重匯的次數。git

// 優化前
  const el = document.getElementById('test');
  el.style.borderLeft = '1px';
  el.style.borderRight = '2px';
  el.style.padding = '5px';

  // 優化後,一次性修改樣式,這樣能夠將三次重排減小到一次重排
  const el = document.getElementById('test');
  el.style.cssText += '; border-left: 1px ;border-right: 2px; padding: 5px;'

2.當咱們要批量修改DOM節點的時候,咱們能夠將DOM節點隱藏掉,而後進行一系列的修改操做,以後再將其設置爲可見,這樣就能夠最多隻進行兩次重排。具體的方法以下:github

// 未優化前
  const ele = document.getElementById('test');
  // 一系列dom修改操做

  // 優化方案一,將要修改的節點設置爲不顯示,以後對它進行修改,修改完成後再顯示該節點,從而只須要兩次重排
  const ele = document.getElementById('test');
  ele.style.display = 'none';
  // 一系列dom修改操做
  ele.style.display = 'block';

  // 優化方案二,首先建立一個文檔片斷(documentFragment),而後對該片斷進行修改,以後將文檔片斷插入到文檔中,只有最後將文檔片斷插入文檔的時候會引發重排,所以只會觸發一次重排。。
  const fragment = document.createDocumentFragment();
  const ele = document.getElementById('test');
  // 一系列dom修改操做
  ele.appendChild(fragment);

3.使用事件委託:事件委託就是將目標節點的事件移到父節點來處理,因爲瀏覽器冒泡的特色,當目標節點觸發了該事件的時候,父節點也會觸發該事件。所以,由父節點來負責監聽和處理該事件。web

那麼,它的優勢在哪裏呢?假設你有一個列表,裏面每個列表項都須要綁定相同的事件,而這個列表可能會頻繁的插入和刪除。若是按照日常的方法,你只能給每個列表項都綁定一個事件處理器,而且,每當插入新的列表項的時候,你也須要爲新的列表項註冊新的事件處理器。這樣的話,若是列表項很大的話,就會致使有特別多的事件處理器,形成極大的性能問題。而經過事件委託,咱們只須要在列表項的父節點監聽這個事件,由它來統一處理就能夠了。這樣,對於新增的列表項也不須要作額外的處理。並且事件委託的用法其實也很簡單:

function handleClick(target) {
  // 點擊列表項的處理事件
}
function delegate (e) {
  // 判斷目標對象是否爲列表項
  if (e.target.nodeName === 'LI') {
    handleClick(e.target);
  }
}
const parent = document.getElementById('parent');
parent.addEventListener('click', delegate);

本文地址在->本人博客地址, 歡迎給個 start 或 follow

相關文章
相關標籤/搜索