天天看點

script 的三種加載模式:預設加載、defer、async

大家好,我是前端西瓜哥。今天我們來了解一下 script 腳本的三種加載方式。

script 的三種加載模式:預設加載、defer、async

預設加載

一般的 script 寫法為:

<script src="app.js"></script>
           

這種寫法有一個問題:它會 阻塞 HTML 的 DOM 建構。

假如我們在 head 元素中使用了 script 腳本,它就會阻止後面元素的渲染,包括 body 元素,此時執行document.querySeletor('body') 拿到的是 null。

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script>
    // 拿到 null
   console.log(document.querySeletor('body'));
  </script>
</head>
<body></body>
</html>
           

此外,當腳本足夠大,加載執行足夠久時,會導緻頁面長時間沒能渲染出完整頁面。

這也是我們将業務代碼腳本放到 body 最下邊的原因,這樣能確定腳本能夠通路一個完整的 DOM 樹,也不會阻止頁面的渲染。

缺點是,HTML 很長的時候,解析到腳本就會花上一點時間,然後才會請求對應的腳本資源。

不過通常來說,HTML 内容都比較簡單,二者感受不到太大差別,除非你網很卡。

defer 加載

<script defer src="app.js"></script>
           

defer,“延遲” 之意。這裡的延遲,指的是延遲執行腳本,下載下傳則不會被阻塞。

需要注意的是, defer 屬性對内嵌腳本無效。畢竟腳本内容就在 HTML 裡了,完全不需要請求資源了好吧。

給 script 标簽添加了 defer 屬性後,腳本不會阻塞 DOM 樹的建構,會先下載下傳資源,然後等待到在 DOMContentLoaded 事件前執行。

DOMContentLoaded 事件的觸發時機為初始 HTML 被建構完成時,此時 CSS、圖檔等資源不需要加載完,但我們的腳本要執行完。

如果多個 script 設定了 defer 屬性,這幾個 script 的執行順序和聲明順序相同,即最前面的腳本先執行。并不是誰先下載下傳誰先執行。

實際開發中,我們可以将業務代碼腳本加上 defer 屬性,放到更上層的 head 标簽下。

這也是最新版 HtmlWebpackPlugin 插件的預設引入打包腳本的方式。

async 加載

<script async src="app.js"></script>
           

async,“異步” 之意。同樣對内嵌腳本無效。

設定 async 後,腳本一旦被下載下傳好了就會執行,不管什麼時機。

适合與執行順序無關的腳本,比如廣告、網站流量分析腳本。

比如插入 Google 分析腳本:

<script async src="//www.google-analytics.com/analytics.js"></script>
           

動态加載

還有一種用腳本加載腳本的特殊情況,這裡也說一說。

<script>
  const script = document.createElement('script');
  script.src = 'app-a.js';
  document.body.appendChild(script);
</script>
           

腳本裡建立一個 script 元素,設定好 src,然後加載到 DOM 樹上,接着腳本就會下載下傳和執行了。

建立的 script 元素預設會給 async 設定為 true,即一旦下載下傳好就立即執行。

如果你要加載有依賴關系的多個腳本,就需要将 async 設定為 false。

<script>
  const script = document.createElement('script');
  // 取消 async 加載方式
  script.async = false;
  script.src = 'app-a.js';
  document.body.appendChild(script);

  const script2 = document.createElement('script');
  script2.async = false;
  script2.src = 'app-b.js';
  document.body.appendChild(script2);
</script>
<script>console.log('我還是所有腳本中最先執行的')</script>
           

這樣寫,就能保證先執行 app-a.js,再執行 app-b.js

但 它無法做到比 HTML 中其他非動态加載的 script 腳本更早執行,這點需要注意。

結尾

script 有三種常見加載模式:

  • 預設加載:會阻塞 DOM 建構
  • defer 加載:下載下傳照舊,但執行延後
  • async:下載下傳完就立即執行,适合沒有依賴的腳本

此外還有動态加載的腳本的情況,這種腳本預設為 async 加載形式,可通過将 async 屬性設定為 false 來解除,讓腳本順序執行。

我是前端西瓜哥,歡迎關注我。

繼續閱讀