1. <script>
標簽
將JavaScript代碼嵌入到HTML中主要方式是使用<script>
元素。
使用<script>
的方式有兩種:
(1)直接在網頁中嵌入JavaScript代碼:
<script>function sayHi() {console.log("Hi");}
</script>
包含在<script>
中的代碼會被從上到下執行,在<script>
元素中的代碼被計算完成之前,頁面的其余內容不會被加載也不會被顯示。
(2)引入外部文件中的代碼,使用src
屬性,該屬性的值是一個URL,指向包含js代碼的文件。
<script src="example.js"></script>
與解釋行內的js代碼一樣,在解釋外部的js文件時,頁面也會阻塞,阻塞的時間也包含下載文件的時間(不使用defer和async的情況下)。
如果使用了src屬性,就不應該在<script>
和</script>
之間再包含其他js代碼。如果兩者都提供的話,則瀏覽器只會下載并執行腳本文件,從而忽略行內代碼。
在沒有使用defer
或async
屬性時,瀏覽器會按照<script>
在頁面中出現的順序依次解釋它們。第二個<script>
元素的代碼必須在第一個<script>
元素的代碼解釋完畢才能開始解釋,依次類推。
2. <script>
標簽位置
瀏覽器的渲染引擎與js引擎是互斥的,當HTML解析器遇到一個script
標簽時,它就會暫停渲染過程,將控制權交給js引擎。js引擎對內聯的js代碼會直接執行,對外部的js文件要先獲取,再執行。等js引擎運行完畢,瀏覽器又會把控制權還給渲染引擎,繼續DOM和CSSOM的構建。
過去,所有的script
標簽都被放在頁面的<head>
標簽內,如下:
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title><script src="example1.js"></script><script src="example2.js"></script>
</head>
這種做法的主要目的是將外部CSS和JavaScript文件集中在一起。然后所有的JS文件都放在<head>
內,意味著所有的JavaScript代碼都下載、解析和解釋完畢后,才能開始渲染頁面(頁面在瀏覽器解析到<body>
的起始標簽時開始渲染)。如果引入的腳本過多,會導致頁面的渲染明顯延遲,瀏覽器窗口完全空白。
為了解決這個問題,現在通常將所有JavaScript的引用放在<body>
元素中的頁面內容后面:
<body>......<script src="example1.js"></script><script src="example2.js"></script></body>
這樣會在處理js代碼之前完全的渲染頁面,用戶會感覺頁面加載更快了。
可以發現,沒有加defer和async屬性的腳本,在遇到<script>
標簽時,首先開始下載腳本,從腳本開始下載到腳本完成執行,整個過程中,HTML的解析都是停止的。當腳本解析完成后,HTML解析繼續。
3. <script>
標簽的defer屬性
defer屬性只對外部的腳本文件有效,給<script>
標簽添加defer屬性,可以推遲腳本的執行。在遇到腳本時,還是會立即下載,但是會延遲執行。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><script src="./scripts/a.js" defer></script></head><body><ul><li>HTML</li><li>CSS</li><li>JAVASCRIPT</li></ul></body>
</html>
上面的<script>
標簽雖然包含在<head>
中,由于加上了defer屬性,它們會在瀏覽器解析到結束的</html>
標簽后才會執行。
HTML5規范要求腳本應該按照它們出現的順序執行,因此第一個推遲的腳本會在第二個推遲的腳本之前執行,而且兩者都會在DOMContentLoaded
事件之前執行。但是在實際情況下,推遲執行的腳本不一定總會按順序執行,或者在DOMContentLoaded
事件之前執行。因此最好只包含一個這樣的腳本。
可以發現,對于defer的腳本,腳本下載時,HTML仍然在解析,當HTML解析完成以后,開始執行腳本,即延遲執行。
4. <script>
標簽的async屬性
async屬性也只適用于外部腳本,瀏覽器同樣會立即下載腳本,但是與defer不同的是,async的腳本不能保證按照它們出現的次序執行,而且當該腳本下載完成后就會立即執行該腳本。由于腳本大小不一樣,下載完成的所需要的時間不同,所以這些腳本不能保證按照出現順序執行。
可以發現,對于async的腳本,腳本下載時,HTML仍然在解析,但是與defer不同的是,當腳本下載完成后立即就會執行腳本。
腳本下載可能很快,此時HTML還沒有完成解析(DOMContentLoaded事件還沒觸發),首先暫停HTML解析,去執行腳本,之后繼續解析。
腳本下載也可能較慢,此時HTML解析已經完成了(DOMContentLoaded事件已經觸發了),直接執行腳本。
async屬性的腳本會保證在頁面的load事件之前執行,但是可能在DOMContentLoaded事件之前或者之后執行,由于無法確定該腳本執行時機,異步腳本不應該在加載期間操作DOM。
5. 如何使用
- 如果腳本是一個模塊并且不依賴于其他任何腳本,則使用async
- 如果腳本依賴于其他腳本或者被其他腳本依賴,則使用defer
- 如果該腳本很小并且被一個async的腳本依賴,則使用內聯的腳本,并且把該腳本放在async腳本的上面。
參考[async vs defer attributes](async vs defer attributes - Growing with the Web)
js高程四