首頁 >web前端 >js教程 >javascript效能優化的方法介紹(附範例)

javascript效能優化的方法介紹(附範例)

不言
不言轉載
2018-11-17 15:03:412473瀏覽

這篇文章帶給大家的內容是關於javascript效能優化的方法介紹(附範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

本文主要是在我讀《高效能Javascript》之後,想要記錄下一些有用的優化方案,並且就我本身的一些經驗,來大家一起分享下,

#Javascript的載入與執行

大家都知道,瀏覽器在解析DOM樹的時候,當解析到script標籤的時候,會阻塞其他的所有任務,直到該js檔案下載、解析執行完成後,才會繼續往下執行。因此,這個時候瀏覽器就會被阻塞在這裡,如果將script標籤放在head裡的話,那麼在該js檔案載入執行前,使用者只能看到空白的頁面,這樣的使用者體驗肯定是特別爛。對此,常用的方法有以下:

  • 將所有的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標籤來載入腳本,例如透過以下程式碼

  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文件,不過透過這種方式的話,就可能面臨跨域的問題。範例如下:

  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檔案合併為同一個,並且進行程式碼壓縮。

javascript作用域

當一個函數執行的時候,會產生一個執行上下文,這個執行上下文定義了函數執行時的環境。當函數執行完畢後,這個執行上下文就會被銷毀。因此,多次呼叫同一個函數會導致建立多個執行上下文。每隔執行上下文都有自己的作用域鏈。相信大家應該早就知道了作用域這個東西,對於一個函數而言,其第一個作用域就是它函數內部的變數。在函數執行過程中,每遇到一個變量,都會搜尋函數的作用域鏈找到第一個匹配的變量,首先查找函數內部的變量,之後再沿著作用域鏈逐層尋找。因此,若我們要存取最外層的變數(全域變數),則比起直接存取內部的變數而言,會帶來比較大的效能損耗。因此,我們可以將經常使用的全域變數參考儲存在一個局部變數裡

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

物件的讀取

javascript中,主要分為字面量、局部變數、陣列元素和物件這四種。存取字面量和局部變數的速度最快,而存取陣列元素和物件成員相對較慢。而訪問物件成員的時候,就和作用域鏈一樣,就是在原型鏈(prototype)上進行查找。因此,若查找的成員在原型鏈位置太深,則存取速度越慢。因此,我們應該盡可能的減少物件成員的查找次數和巢狀深度。例如以下程式碼

  // 进行两次对象成员查找
  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的樣式進行修改的時候,我們應該盡可能的合併所有的修改並且一次處理,減少重排和重彙的次數。

  // 优化前
  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节点隐藏掉,然后进行一系列的修改操作,之后再将其设置为可见,这样就可以最多只进行两次重排。具体的方法如下:

  // 未优化前
  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.使用事件委托:事件委托就是将目标节点的事件移到父节点来处理,由于浏览器冒泡的特点,当目标节点触发了该事件的时候,父节点也会触发该事件。因此,由父节点来负责监听和处理该事件。

那么,它的优点在哪里呢?假设你有一个列表,里面每一个列表项都需要绑定相同的事件,而这个列表可能会频繁的插入和删除。如果按照平常的方法,你只能给每一个列表项都绑定一个事件处理器,并且,每当插入新的列表项的时候,你也需要为新的列表项注册新的事件处理器。这样的话,如果列表项很大的话,就会导致有特别多的事件处理器,造成极大的性能问题。而通过事件委托,我们只需要在列表项的父节点监听这个事件,由它来统一处理就可以了。这样,对于新增的列表项也不需要做额外的处理。而且事件委托的用法其实也很简单:

function handleClick(target) {
  // 点击列表项的处理事件
}
function delegate (e) {
  // 判断目标对象是否为列表项
  if (e.target.nodeName === 'LI') {
    handleClick(e.target);
  }
}
const parent = document.getElementById('parent');
parent.addEventListener('click', delegate);


以上是javascript效能優化的方法介紹(附範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除