script標籤
針對 <script>
標籤放哪裡,一般會有兩種版本:
- 放在
<head> ... </head>
之間 - 放在
</body>
之前
把<script>
標籤放在 </body>
之前,網頁可以正常運作:
1 | <!DOCTYPE html> |
接著,我們試著把 <script>
標籤移到 <head> ... </head>
之間,這時候會發現網頁一片空白:
1 | <!DOCTYPE html> |
圖片來源:Asynchronous vs Deferred JavaScript
瀏覽器解析 HTML 是一行一行依序向下讀取,在傳統的寫法中,當瀏覽器讀到 <script>
時,便會 暫停解析 DOM,立刻開始下載 <script>
的資源,並在下載完成後立刻執行。由於這樣的特性,便可能造成在 DOM 樹建構不完全時就執行 JavaScript,其中需要操作 DOM 的程式可能就因此無法正確運作,許多衍伸的問題也就因此產生;若是透過 src
外聯 .js
檔案,瀏覽器會「同步地」下載 .js
檔案,在下載完成並執行完程式碼之前,後續的其他資源下載、頁面剖析等,都會被阻斷。執行過程中,使用者便會卡在白畫面,並產生覺得網站太慢、使用者體驗不好等感受。
不過,文件資源的完整載入,是指 HTML
、 CSS
、圖片等都載入完成,而不單指 DOM樹 建立完成;若想在文件剖析完成、 DOM樹 生成時就執行程式碼,建議是將 script
放在文件尾端,通常是 body
標籤之前,因為此時 DOM樹 已經建立,操作 DOM 節點就不是問題了。但在更複雜的網站中,HTML
、JavaScript
的檔案都來越大,下載、執行時間也越來越長,需要等到整個 DOM 樹都載入完成才開始下載 <script>
內的資源,從網站讀取完成到可操作之間便會有明顯的延遲感。
這樣的問題該怎麼解決呢?
async & defer
從 HTML4 開始,<script>
便多了 defer
屬性,HTML5 則多了 async
,兩者皆是用來幫助開發者控制 <script>
內資源的載入及執行順序,以及避免 DOM 的解析被資源下載卡住。這兩個屬性只適用在外部引入的檔案,對內嵌程式碼的script
標籤沒有影響。
defer
defer
意旨為 延遲(Deferred),也就是說,加上 defer
屬性後,瀏覽器會繼續解析、渲染畫面,而不會因為需要載入 <script>
內的資源而卡在那邊等;如果有多個設置 defer
的 js
標籤存在,則會由上到下依照擺放順序觸發。實際上的執行時間,會在 DOM
渲染完畢後,DOMContentLoaded
事件執行之前。
1 | <script defer src="script.js">/script> |
圖片來源:Asynchronous vs Deferred JavaScript
defer 屬性告訴瀏覽器在 HTML 還在解析時加載 js,但是等到 HTML 整個解析完才執行 js。
async
async
即為 非同步(Asynchronous),在 <script>
加上 async
屬性後,與 defer
相同的是會在背景執行下載,但不同的是當下載完成會馬上暫停 DOM 解析(若尚未解析完的話),並開始執行 JavaScript。也因為下載完成後會立即執行,如果有多個 async
屬性的 js
,先下載完的就會先執行,因為下載完成的順序是無法預測的,因此「執行順序也就無法預測」。
1 | <script async src="script.js"></script> |
圖片來源:Asynchronous vs Deferred JavaScript
async
屬性告訴瀏覽器可以異步執行(executed asynchronously),在 HTML
還在解析時加載 js
,當 js
完全下載後才暫停解析 HTML
,執行 js
。
type=”module”
在主流的現代瀏覽器中,<script>
的屬性可以加上 type="module"
。這時,瀏覽器會將此檔案認為是一個 JavaScript 模組,其中的解析規則、執行環境會略有不同;這時候的 <script>
預設行為會像是 defer
一樣,背景下載,且等待 DOM 解析、渲染完成後才執行,也因此 defer
屬性無法在 type="module"
產生作用。但同樣可以透過 async
屬性讓它在下載完成後即刻執行。
使用場景
前面已經針對這兩個屬性進行說明了,那麼該如何正確地使用呢?
defer
由於背景載入、不打斷渲染及確保執行順序的特色,基本上沒特別需求的話,建議設定在 <script>
;當然,<script>
本身的擺放順序還是要稍微留心注意。async
比較特別,因為下載後會立刻執行,且不保證順序,一般常見的應用是設定在完全獨立的小模組,例如 背景 Log、頁面廣告等等,在避免造成使用者體驗變差的同時,盡量提早開始產生效果。
async
及 defer
是專屬於 <script>
的屬性,而網頁中的其他資源,我們可以透過 <link>
的 preload
、prefetch
屬性,來幫我們 延遲載入 未來才需要用到的資源,詳細的請參考 Preload vs Prefetch。