W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
現(xiàn)代的網(wǎng)站中,腳本往往比 HTML 更“重”:它們的大小通常更大,處理時間也更長。
當瀏覽器加載 HTML 時遇到 <script>...</script>
標簽,瀏覽器就不能繼續(xù)構(gòu)建 DOM。它必須立刻執(zhí)行此腳本。對于外部腳本 <script src="..."></script>
也是一樣的:瀏覽器必須等腳本下載完,并執(zhí)行結(jié)束,之后才能繼續(xù)處理剩余的頁面。
這會導(dǎo)致兩個重要的問題:
<p>...content before script...</p>
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
<!-- This isn't visible until the script loads -->
<p>...content after script...</p>
這里有一些解決辦法。例如,我們可以把腳本放在頁面底部。此時,它可以訪問到它上面的元素,并且不會阻塞頁面顯示內(nèi)容:
<body>
...all content is above the script...
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
</body>
但是這種解決方案遠非完美。例如,瀏覽器只有在下載了完整的 HTML 文檔之后才會注意到該腳本(并且可以開始下載它)。對于長的 HTML 文檔來說,這樣可能會造成明顯的延遲。
這對于使用高速連接的人來說,這不值一提,他們不會感受到這種延遲。但是這個世界上仍然有很多地區(qū)的人們所使用的網(wǎng)絡(luò)速度很慢,并且使用的是遠非完美的移動互聯(lián)網(wǎng)連接。
幸運的是,這里有兩個 <script>
特性(attribute)可以為我們解決這個問題:defer
和 async
。
defer
特性告訴瀏覽器不要等待腳本。相反,瀏覽器將繼續(xù)處理 HTML,構(gòu)建 DOM。腳本會“在后臺”下載,然后等 DOM 構(gòu)建完成后,腳本才會執(zhí)行。
這是與上面那個相同的示例,但是帶有 defer
特性:
<p>...content before script...</p>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
<!-- 立即可見 -->
<p>...content after script...</p>
換句話說:
defer
? 特性的腳本不會阻塞頁面。defer
? 特性的腳本總是要等到 DOM 解析完畢,但在 ?DOMContentLoaded
? 事件之前執(zhí)行。下面這個示例演示了上面所說的第二句話:
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
</script>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1" rel="external nofollow" rel="external nofollow" rel="external nofollow" rel="external nofollow" ></script>
<p>...content after scripts...</p>
DOMContentLoaded
? 事件處理程序等待具有 ?defer
? 特性的腳本執(zhí)行完成。它僅在腳本下載且執(zhí)行結(jié)束后才會被觸發(fā)。具有 defer
特性的腳本保持其相對順序,就像常規(guī)腳本一樣。
假設(shè),我們有兩個具有 defer
特性的腳本:long.js
在前,small.js
在后。
<script defer src="https://javascript.info/article/script-async-defer/long.js" rel="external nofollow" rel="external nofollow" ></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js" rel="external nofollow" rel="external nofollow" ></script>
瀏覽器掃描頁面尋找腳本,然后并行下載它們,以提高性能。因此,在上面的示例中,兩個腳本是并行下載的。small.js
可能會先下載完成。
……但是,defer
特性除了告訴瀏覽器“不要阻塞頁面”之外,還可以確保腳本執(zhí)行的相對順序。因此,即使 small.js
先加載完成,它也需要等到 long.js
執(zhí)行結(jié)束才會被執(zhí)行。
當我們需要先加載 JavaScript 庫,然后再加載依賴于它的腳本時,這可能會很有用。
?
defer
? 特性僅適用于外部腳本如果
<script>
腳本沒有src
,則會忽略defer
特性。
async
特性與 defer
有些類似。它也能夠讓腳本不阻塞頁面。但是,在行為上二者有著重要的區(qū)別。
async
特性意味著腳本是完全獨立的:
async
? 腳本而阻塞(與 ?defer
? 類似)。async
? 腳本加載完成,同樣,?async
? 腳本也不會等待其他腳本。DOMContentLoaded
? 和異步腳本不會彼此等待:DOMContentLoaded
? 可能會發(fā)生在異步腳本之前(如果異步腳本在頁面完成后才加載完成)DOMContentLoaded
? 也可能發(fā)生在異步腳本之后(如果異步腳本很短,或者是從 HTTP 緩存中加載的)換句話說,async
腳本會在后臺加載,并在加載就緒時運行。DOM 和其他腳本不會等待它們,它們也不會等待其它的東西。async
腳本就是一個會在加載完成時執(zhí)行的完全獨立的腳本。就這么簡單,現(xiàn)在明白了吧?
下面是一個類似于我們在講 defer
時所看到的例子:long.js
和 small.js
兩個腳本,只是現(xiàn)在 defer
變成了 async
。
它們不會等待對方。先加載完成的(可能是 small.js
)—— 先執(zhí)行:
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
</script>
<script async src="https://javascript.info/article/script-async-defer/long.js" rel="external nofollow" rel="external nofollow" ></script>
<script async src="https://javascript.info/article/script-async-defer/small.js" rel="external nofollow" rel="external nofollow" ></script>
<p>...content after scripts...</p>
async
? 的腳本不會阻塞頁面渲染。DOMContentLoaded
? 可能在 ?async
? 之前或之后觸發(fā),不能保證誰先誰后。small.js
? 排在第二位,但可能會比 ?long.js
? 這個長腳本先加載完成,所以 ?small.js
? 會先執(zhí)行。雖然,可能是 ?long.js
? 先加載完成,如果它被緩存了的話,那么它就會先執(zhí)行。換句話說,異步腳本以“加載優(yōu)先”的順序執(zhí)行。當我們將獨立的第三方腳本集成到頁面時,此時采用異步加載方式是非常棒的:計數(shù)器,廣告等,因為它們不依賴于我們的腳本,我們的腳本也不應(yīng)該等待它們:
<!-- Google Analytics 腳本通常是這樣嵌入頁面的 -->
<script async src="https://google-analytics.com/analytics.js" rel="external nofollow" ></script>
?
async
? 特性僅適用于外部腳本就像
defer
一樣,如果<script>
標簽沒有src
特性(attribute),那么async
特性會被忽略。
此外,還有一種向頁面添加腳本的重要的方式。
我們可以使用 JavaScript 動態(tài)地創(chuàng)建一個腳本,并將其附加(append)到文檔(document)中:
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)
當腳本被附加到文檔 (*)
時,腳本就會立即開始加載。
默認情況下,動態(tài)腳本的行為是“異步”的。
也就是說:
如果我們顯式地設(shè)置了 script.async=false
,則可以改變這個規(guī)則。然后腳本將按照腳本在文檔中的順序執(zhí)行,就像 defer
那樣。
在下面這個例子中,loadScript(src)
函數(shù)添加了一個腳本,并將 async
設(shè)置為了 false
。
因此,long.js
總是會先執(zhí)行(因為它是先被添加到文檔的):
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false;
document.body.append(script);
}
// long.js 先執(zhí)行,因為代碼中設(shè)置了 async=false
loadScript("/article/script-async-defer/long.js");
loadScript("/article/script-async-defer/small.js");
如果沒有 script.async=false
,腳本則將以默認規(guī)則執(zhí)行,即加載優(yōu)先順序(small.js
大概會先執(zhí)行)。
同樣,和 defer
一樣,如果我們要加載一個庫和一個依賴于它的腳本,那么順序就很重要。
async
和 defer
有一個共同點:加載這樣的腳本都不會阻塞頁面的渲染。因此,用戶可以立即閱讀并了解頁面內(nèi)容。
但是,它們之間也存在一些本質(zhì)的區(qū)別:
順序 | DOMContentLoaded
|
|
---|---|---|
async
|
加載優(yōu)先順序。腳本在文檔中的順序不重要 —— 先加載完成的先執(zhí)行 | 不相關(guān)??赡茉谖臋n加載完成前加載并執(zhí)行完畢。如果腳本很小或者來自于緩存,同時文檔足夠長,就會發(fā)生這種情況。 |
defer
|
文檔順序(它們在文檔中的順序) | 在文檔加載和解析完成之后(如果需要,則會等待),即在 DOMContentLoaded 之前執(zhí)行。 |
在實際開發(fā)中,defer
用于需要整個 DOM 的腳本,和/或腳本的相對執(zhí)行順序很重要的時候。
async
用于獨立腳本,例如計數(shù)器或廣告,這些腳本的相對執(zhí)行順序無關(guān)緊要。
沒有腳本的頁面應(yīng)該也是可用的
請注意:如果你使用的是
defer
或async
,那么用戶將在腳本加載完成 之前 先看到頁面。
在這種情況下,某些圖形組件可能尚未初始化完成。
因此,請記得添加一個“正在加載”的提示,并禁用尚不可用的按鈕。以讓用戶可以清楚地看到,他現(xiàn)在可以在頁面上做什么,以及還有什么是正在準備中的。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: