這是一個偽命題嗎?
首先,W3C 推薦 script 腳本應該被立即加載和執行,其次,經過網絡搜索,我只發現了 1 例相同的問題,所以這個問題的真偽其實還有待進一步驗證,但是從邏輯上説,瀏覽器會並行加載靜態資源,對於 Chrome,可以並行加載 6 個資源,如果其中一個資源獲取的比較緩慢,那麼會影響串行的下 6 個請求的發送,如果能夠預先測試出 6 個通暢的請求,一併發送,那麼就可以提升網絡加載的整體性能。但瀏覽器是否有這一層優化呢?目前我只見到這篇文章提到過瀏覽器似乎有這個優化算法,但是並沒有在其他地方得到確認。
標籤的價值
我們有兩種方式使用 <script> 標籤:
- 通過設置
src屬性引入外部 JavaScript 靜態資源; - 執行
<script>開閉標籤內的 JavaScript 腳本;
但其實本質上這兩種方式是一回事,其最終的目的就是讓瀏覽器在當前頁面執行 JavaScript 腳本,只不過對於前者而言多了一道工序:將服務器返回的 JavaScript 腳本內容插入 <script> 標籤內部,然後在執行它。
因此,對於 <script> 標籤,我們唯一關心的只有一點:JavaScript 腳本被執行的時機。
標籤的加載順序
在頁面中,我們有兩處地方可以放置 <script> 標籤:
<head> ... </head>head 標籤內部;<body> ... </body>body 標籤內部;
在 <head> 標籤中插入引用外部 JavaScript 會導致 <body> 標籤內的內容在 JavaScript 被完全下載,解析,執行完畢後才會被解析,這期間用户會看到瀏覽器一片空白,因此會影響用户體驗。(這是由於瀏覽器從上至下解析 HTML 文檔,而 JavaScript 的下載,解析和執行會中止瀏覽器的解析過程)。
因此業界通行的做法是,將 script 標籤放置 <body> 底部,從而避免 JavaScript 阻塞頁面渲染。
但無論如何,我們的 JS 腳本的執行順序是相同的:根據其在頁面中的位置決定先後順序。
但是我們可以通過兩個屬性改變這一順序。
script 常用屬性:defer 和 async
async 屬性
async 屬性是 HTML5 規範新推出的一個屬性,用來告知瀏覽器應該儘可能的異步加載腳本。所有的瀏覽器都支持
該屬性。具有該屬性的腳本我們既無法得知它下載的時間,也無法得知它執行的時機,我們唯一知道的只有兩點:
- 腳本會被異步下載;
- 腳本下載完畢後會立即執行,此時會阻止 HTML 的渲染;
⚠️ 注意,script 標籤必須有 src 屬性,且屬性值有效。
defer 屬性
defer 屬性向瀏覽器指明瞭腳本被執行的時機:“文檔解析之後,DOMContentLoaded 事件被觸發之前(即 HTML 文檔被完全加載和解析,不管樣式表,圖片或 iframe 是否加載完畢。恩,一個很微妙的時間 ?)”,這裏需要注意的是,並不是具有 defer 屬性的腳本會等待 DOMContentLoaded 的觸發,並趕在這之前執行,而是具有 defer 屬性的腳本會延遲 DOMContentLoaded 事件的觸發。
理論上講所有帶有 defer 屬性的腳本會按照在 HTML 中定義的順序被依次觸發,但遺憾的是實際中好像並不會這樣(此處有待做實驗進一步驗證)。
特別需要注意的是,帶有 defer 關鍵字的腳本也是以異步的形式被加載的。
⚠️ 注意,script 標籤必須有 src 屬性,且屬性值有效。
小結
讓我們總結一下,<script> 腳本默認被瀏覽器以一定順序並行下載,並按照定義的順序依次執行(在這期間,加載好的代碼安靜的待在瀏覽器緩存中,直到所有前置的腳本被加載和解析完成)。
我們有兩種方式更改 <script> 的下載和執行時機,通過屬性 async 和 defer,這兩個屬性都會採用異步的方式下載腳本,而他們的區別在於:添加了 async 屬性的腳本會被瀏覽器異步加載,但我們無法得知其被下載和執行的時間,而添加了 defer 屬性的腳本將會在文檔解析後,DOMContentLoaded 事件觸發前被執行。
最後,讓我們再想想這兩個屬性的使用時機:
async:由於我們無法知道添加了async屬性腳本的具體下載,執行時間,因此所有具有依賴關係或操作 DOM 元素的腳本都不適宜添加該屬性,反過來説,任何不具備依賴關係,不操作 DOM 的腳本都可以添加該屬性以節約腳本的下載時間,什麼類型的腳本滿足上述要求呢?我能想到的有埋點類腳本,例如訪問統計腳本,廣告流量統計腳本等;defer:該關鍵字給我們的腳本一個完美的下載,執行時機,在 DOM 準備好之後,要不是現實中並非依次加載帶有defer屬性的腳本,我幾乎就要建議你給所有腳本添加該屬性了,它既可以保障我們的腳本被異步加載,又可以使我們獲得 DOM 操作的確定性,除了內聯腳本,應該給每個沒有依賴的腳本都添加該屬性。