script標籤
當我們要在頁面當中引入指令碼的時候,一般用的是script標籤(i.e. <script>)。很多人對script標籤的第一印象就是- 阻塞。在High Performance Web Sites 雅虎軍規第6條中也提到儘量把script指令碼放在body尾部。通過一個小例子我們看看script放在不同位置所產生的不同效果最後得出一丟丟優化結論。
首先明確一點,當在html頁面裡引入script時,瀏覽器做了2件事情:
- 獲取/載入指令碼內容,這部分不會阻塞!
- 執行獲取的指令碼內容,會阻塞!
假設我們有2個指令碼:
//script1.js
let t1 = +new Date;
console.log(`script1 is loading at`, t1);
console.log(`script1 element`, document.getElementById(`load-experiment`));
while((+new Date) - t1 < 1000) {
// delay 1 seconds
}
console.log(`script1 finishes loading at`, +new Date);複製程式碼
//script2.js
let t2 = +new Date;
console.log(`script2 is loading at`, t2);
console.log(`script2 element`, document.getElementById(`load-experiment`));
while((+new Date) - t2 < 2000) {
// delay 2 seconds
}
console.log(`script2 finishes loading at`, +new Date);複製程式碼
把script標籤都放head裡
<!--all-in-head.html-->
<html>
<head>
<title> test js tag async and defer attributes</title>
<script src=`./script1.js`></script>
<script src=`./script2.js`></script>
</head>
<body>
<h1 id=`load-experiment`> hello world </h1>
</body>
</html>複製程式碼
console裡的輸出為:
script1 is loading at 1496747869008
script1.js:4 script1 element null
script1.js:8 script1 finishes loading at 1496747870008
script2.js:2 script2 is loading at 1496747870009
script2.js:4 script2 element null
script2.js:8 script2 finishes loading at 1496747872009複製程式碼
發現:
- 用瀏覽器開啟這個html會發現有明顯的空白時間。這是因為script1跟script2的 執行 阻塞了DOM的載入。
- script指令碼執行的時候並不能獲取DOM元素(getElementById返回null)。驗證了上一條
- script之間也是相互阻塞的。script2是等script1執行好後才執行。
把script標籤都放body尾部
這也是雅虎軍規High Performance Web Sites第6條裡的建議。
<!--all-in-body.html-->
<html>
<head>
<title> test js tag async and defer attributes</title>
</head>
<body>
<h1 id=`load-experiment`> hello world </h1>
<script src=`./script1.js`></script>
<script src=`./script2.js`></script>
</body>
</html>複製程式碼
console裡的輸出為:
script1 is loading at 1496751597679
script1.js:4 script1 element <h1 id="load-experiment"> hello world </h1>
script1.js:8 script1 finishes loading at 1496751598679
script2.js:2 script2 is loading at 1496751598680
script2.js:4 script2 element <h1 id="load-experiment"> hello world </h1>
script2.js:8 script2 finishes loading at 1496751600680複製程式碼
發現:
- 用瀏覽器開啟這個html不會有明顯的空白時間。這是因為script1跟script2的發生在DOM載入後。
- script指令碼執行的時候能獲取DOM元素(getElementById返回節點)。驗證了上一條
- script之間是相互阻塞的。script2是等script1執行好後才執行。
把所有的script指令碼放body尾部的情況過於理想化了。例如body裡有些內聯JS需要引用到的功能程式碼就需要放head裡提前執行好。最常見的例子就是計算首屏載入時間(Above The Fold),首屏載入時間肯定是不能等DOM載入完。這時候一般是在head里載入功能庫並觸發首屏載入開始計時,然後在首屏的末尾處用內聯JS觸發首屏載入結束計時。這時候就需要有些程式碼是在head裡的。
script標籤相應地放head跟body裡
根據需要把script指令碼相應地放在head跟body裡。這是最常見的情況。
<html>
<head>
<script src="headScripts.js"></scripts>
</head>
<body>
<h1 id=`load-experiment`> hello world </h1>
<script src="bodyScripts.js"></script>
</body>
</html>複製程式碼
defer!
把script放body尾的話有個缺點就是要等DOM載入好了才會去載入並執行script指令碼。前面開頭的時候說過獲取/載入指令碼內容不會阻塞,只有執行指令碼內容的時候才會阻塞!如果能在載入DOM的同時載入script指令碼,等到DOM解析好了再執行指令碼,這樣就可以節省部分載入指令碼的時間。當指令碼比較大時提高的效率會很可觀。於是就有了script標籤裡的defer屬性: 並行載入該指令碼,直到DOM解析完了並且它前面含有defer的指令碼都執行完後,才可以執行該指令碼。
<!--defer.html-->
<html>
<head>
<title> test js tag async and defer attributes</title>
<script defer src=`./script1.js`></script>
<script defer src=`./script2.js`></script>
</head>
<body>
<h1 id=`load-experiment`> hello world </h1>
</body>
</html>複製程式碼
console裡的輸出為:
script1 is loading at 1496760312686
script1.js:4 script1 element <h1 id="load-experiment"> hello world </h1>
script1.js:8 script1 finishes loading at 1496760313686
script2.js:2 script2 is loading at 1496760313686
script2.js:4 script2 element <h1 id="load-experiment"> hello world </h1>
script2.js:8 script2 finishes loading at 1496760315686複製程式碼
發現:
- 跟把script放body的情況輸出一樣。從可以獲取到DOM元素可以看出它會等到DOM解析好再執行。
- 新增了defer屬性的指令碼是會按順序執行的。script1先於script2執行。
async!
通常頁面上會有些附加功能元件。這種元件相互獨立並且不是該頁面必需元件。也就是說該種指令碼無執行順序要求(因為獨立)並且就算載入或者執行失敗也不會對頁面造成致命影響的。例如頁面上的評論功能跟聊天功能。
<!--async.html-->
<html>
<head>
<title> test js tag async and defer attributes</title>
</head>
<body>
<h1 id=`load-experiment`> hello world </h1>
<script async src=`./script1.js`></script>
<script async src=`./script2.js`></script>
</body>
</html>複製程式碼
多次重新整理頁面可以看到每次輸出結果不一樣。體現在:
- scrip1跟script2的執行順序在變
- 獲取DOM元素的結果在變。有時能獲取到有時獲取不到
頁面上加了async的指令碼無法保證其執行順序,這通常也是隱藏bug的來源。除非你確定該指令碼跟頁面其他指令碼無依賴關係,不然慎用async吧。
總結
綜上所述,最後引入指令碼的形式大概是這樣滴:
<html>
<head>
<!--headScripts.js為必須在DOM解析前載入並執行的指令碼-->
<script src="headScripts.js"></script>
</head>
<body>
<!--body內容-->
<h1 id=`load-experiment`> hello world </h1>
<!--獨立的元件,不依賴DOM, 錦上添花型的,可用async-->
<script async src="forumWidget.js"></script>
<script async src="chatWidget.js"></script>
</body>
</html>複製程式碼
Reference
Notice
- 如果您覺得該Repo讓您有所收穫,請「Star 」支援樓主。
- 如果您想持續關注樓主的最新系列文章,請「Watch」訂閱