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

預設加載
一般的 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 來解除,讓腳本順序執行。
我是前端西瓜哥,歡迎關注我。