腳本加載:async,defer

現(xiàn)代的網(wǎng)站中鲸睛,腳本往往比 HTML 更“重”:它們的大小通常更大娜饵,處理時(shí)間也更長(zhǎng)。

當(dāng)瀏覽器加載 HTML 時(shí)遇到 <script>...</script> 標(biāo)簽官辈,瀏覽器就不能繼續(xù)構(gòu)建 DOM箱舞。它必須立刻執(zhí)行此腳本。對(duì)于外部腳本 <script src="..."></script> 也是一樣的:瀏覽器必須等腳本下載完拳亿,并執(zhí)行結(jié)束晴股,之后才能繼續(xù)處理剩余的頁(yè)面。

這會(huì)導(dǎo)致兩個(gè)重要的問(wèn)題:

  1. 腳本不能訪問(wèn)到位于它們下面的 DOM 元素肺魁,因此电湘,腳本無(wú)法給它們添加處理程序等。
  2. 如果頁(yè)面頂部有一個(gè)笨重的腳本,它會(huì)“阻塞頁(yè)面”胡桨。在該腳本下載并執(zhí)行結(jié)束前枝缔,用戶都不能看到頁(yè)面內(nèi)容:
<p>...content before script...</p>  
<script src="[https://javascript.info/article/script-async-defer/long.js?speed=1](https://javascript.info/article/script-async-defer/long.js?speed=1)"></script>  <!-- This isn't visible until the script loads -->  
<p>...content after script...</p>

這里有一些解決辦法。例如训唱,我們可以把腳本放在頁(yè)面底部鸽斟。此時(shí),它可以訪問(wèn)到它上面的元素呢诬,并且不會(huì)阻塞頁(yè)面顯示內(nèi)容:

<body> ...all content is above the script...
   <script src="[https://javascript.info/article/script-async-defer/long.js?speed=1](https://javascript.info/article/script-async-defer/long.js?speed=1)"></script>  
</body>

但是這種解決方案遠(yuǎn)非完美涌哲。例如,瀏覽器只有在下載了完整的 HTML 文檔之后才會(huì)注意到該腳本(并且可以開(kāi)始下載它)尚镰。對(duì)于長(zhǎng)的 HTML 文檔來(lái)說(shuō)阀圾,這樣可能會(huì)造成明顯的延遲。

這對(duì)于使用高速連接的人來(lái)說(shuō)狗唉,這不值一提初烘,他們不會(huì)感受到這種延遲。但是這個(gè)世界上仍然有很多地區(qū)的人們所使用的網(wǎng)絡(luò)速度很慢分俯,并且使用的是遠(yuǎn)非完美的移動(dòng)互聯(lián)網(wǎng)連接肾筐。

幸運(yùn)的是,這里有兩個(gè) <script> 特性(attribute)可以為我們解決這個(gè)問(wèn)題:deferasync缸剪。

defer

defer 特性告訴瀏覽器不要等待腳本吗铐。相反,瀏覽器將繼續(xù)處理 HTML杏节,構(gòu)建 DOM唬渗。腳本會(huì)“在后臺(tái)”下載,然后等 DOM 構(gòu)建完成后奋渔,腳本才會(huì)執(zhí)行镊逝。

這是與上面那個(gè)相同的示例,但是帶有 defer 特性:

<p>...content before script...</p>  
<script defer src="[https://javascript.info/article/script-async-defer/long.js?speed=1](https://javascript.info/article/script-async-defer/long.js?speed=1)"></script> 
<!-- 立即可見(jiàn) --> 
<p>...content after script...</p>

換句話說(shuō):

  • 具有 defer 特性的腳本不會(huì)阻塞頁(yè)面卒稳。
  • 具有 defer 特性的腳本總是要等到 DOM 解析完畢蹋半,但在 DOMContentLoaded 事件之前執(zhí)行。

下面這個(gè)示例演示了上面所說(shuō)的第二句話:

<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](https://javascript.info/article/script-async-defer/long.js?speed=1)">
</script> 

<p>...content after scripts...</p>

  1. 頁(yè)面內(nèi)容立即顯示充坑。
  2. DOMContentLoaded 事件處理程序等待具有 defer 特性的腳本執(zhí)行完成减江。它僅在腳本下載且執(zhí)行結(jié)束后才會(huì)被觸發(fā)。

具有 defer 特性的腳本保持其相對(duì)順序捻爷,就像常規(guī)腳本一樣辈灼。

假設(shè),我們有兩個(gè)具有 defer 特性的腳本:long.js 在前也榄,small.js 在后巡莹。

<script defer src="[https://javascript.info/article/script-async-defer/long.js](https://javascript.info/article/script-async-defer/long.js)"></script>  
<script defer src="[https://javascript.info/article/script-async-defer/small.js](https://javascript.info/article/script-async-defer/small.js)"></script>

瀏覽器掃描頁(yè)面尋找腳本司志,然后并行下載它們,以提高性能降宅。因此骂远,在上面的示例中,兩個(gè)腳本是并行下載的腰根。small.js 可能會(huì)先下載完成激才。

……但是,defer 特性除了告訴瀏覽器“不要阻塞頁(yè)面”之外额嘿,還可以確保腳本執(zhí)行的相對(duì)順序瘸恼。因此,即使 small.js 先加載完成册养,它也需要等到 long.js 執(zhí)行結(jié)束才會(huì)被執(zhí)行东帅。

當(dāng)我們需要先加載 JavaScript 庫(kù),然后再加載依賴于它的腳本時(shí)球拦,這可能會(huì)很有用靠闭。

defer 特性僅適用于外部腳本

如果 <script> 腳本沒(méi)有 src,則會(huì)忽略 defer 特性刘莹。

async

async 特性與 defer 有些類似阎毅。它也能夠讓腳本不阻塞頁(yè)面焚刚。但是点弯,在行為上二者有著重要的區(qū)別。

async 特性意味著腳本是完全獨(dú)立的:

  • 瀏覽器不會(huì)因 async 腳本而阻塞(與 defer 類似)矿咕。
  • 其他腳本不會(huì)等待 async 腳本加載完成抢肛,同樣,async 腳本也不會(huì)等待其他腳本碳柱。
  • DOMContentLoaded 和異步腳本不會(huì)彼此等待:
    • DOMContentLoaded 可能會(huì)發(fā)生在異步腳本之前(如果異步腳本在頁(yè)面完成后才加載完成)
    • DOMContentLoaded 也可能發(fā)生在異步腳本之后(如果異步腳本很短捡絮,或者是從 HTTP 緩存中加載的)

換句話說(shuō),async 腳本會(huì)在后臺(tái)加載莲镣,并在加載就緒時(shí)運(yùn)行福稳。DOM 和其他腳本不會(huì)等待它們,它們也不會(huì)等待其它的東西瑞侮。async 腳本就是一個(gè)會(huì)在加載完成時(shí)執(zhí)行的完全獨(dú)立的腳本的圆。就這么簡(jiǎn)單,現(xiàn)在明白了吧半火?

下面是一個(gè)類似于我們?cè)谥v defer 時(shí)所看到的例子:long.jssmall.js 兩個(gè)腳本越妈,只是現(xiàn)在 defer 變成了 async

它們不會(huì)等待對(duì)方钮糖。先加載完成的(可能是 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](https://javascript.info/article/script-async-defer/long.js)"></script> 

<script async src="[https://javascript.info/article/script-async-defer/small.js](https://javascript.info/article/script-async-defer/small.js)"></script>  

<p>...content after scripts...</p>

  • 頁(yè)面內(nèi)容立刻顯示出來(lái):加載寫有 async 的腳本不會(huì)阻塞頁(yè)面渲染梅掠。
  • DOMContentLoaded 可能在 async 之前或之后觸發(fā),不能保證誰(shuí)先誰(shuí)后。
  • 較小的腳本 small.js 排在第二位阎抒,但可能會(huì)比 long.js 這個(gè)長(zhǎng)腳本先加載完成酪我,所以 small.js 會(huì)先執(zhí)行。雖然且叁,可能是 long.js 先加載完成祭示,如果它被緩存了的話,那么它就會(huì)先執(zhí)行谴古。換句話說(shuō)质涛,異步腳本以“加載優(yōu)先”的順序執(zhí)行。

當(dāng)我們將獨(dú)立的第三方腳本集成到頁(yè)面時(shí)掰担,此時(shí)采用異步加載方式是非常棒的:計(jì)數(shù)器汇陆,廣告等,因?yàn)樗鼈儾灰蕾囉谖覀兊哪_本带饱,我們的腳本也不應(yīng)該等待它們:

<!-- Google Analytics 腳本通常是這樣嵌入頁(yè)面的 -->  
<script async src="[https://google-analytics.com/analytics.js](https://google-analytics.com/analytics.js)"></script>

此外毡代,還有一種向頁(yè)面添加腳本的重要的方式。

我們可以使用 JavaScript 動(dòng)態(tài)地創(chuàng)建一個(gè)腳本勺疼,并將其附加(append)到文檔(document)中:

let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)

當(dāng)腳本被附加到文檔 (*) 時(shí)教寂,腳本就會(huì)立即開(kāi)始加載。

默認(rèn)情況下执庐,動(dòng)態(tài)腳本的行為是“異步”的酪耕。

也就是說(shuō):

  • 它們不會(huì)等待任何東西,也沒(méi)有什么東西會(huì)等它們轨淌。
  • 先加載完成的腳本先執(zhí)行(“加載優(yōu)先”順序)迂烁。

如果我們顯式地設(shè)置了 script.async=false,則可以改變這個(gè)規(guī)則递鹉。然后腳本將按照腳本在文檔中的順序執(zhí)行盟步,就像 defer 那樣。

在下面這個(gè)例子中躏结,loadScript(src) 函數(shù)添加了一個(gè)腳本却盘,并將 async 設(shè)置為了 false

因此媳拴,long.js 總是會(huì)先執(zhí)行(因?yàn)樗窍缺惶砑拥轿臋n的):

function loadScript(src) {
  let script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.body.append(script);
}

// long.js 先執(zhí)行黄橘,因?yàn)榇a中設(shè)置了 async=false
loadScript("/article/script-async-defer/long.js");
loadScript("/article/script-async-defer/small.js");

如果沒(méi)有 script.async=false,腳本則將以默認(rèn)規(guī)則執(zhí)行禀挫,即加載優(yōu)先順序(small.js 大概會(huì)先執(zhí)行)旬陡。

同樣,和 defer 一樣语婴,如果我們要加載一個(gè)庫(kù)和一個(gè)依賴于它的腳本描孟,那么順序就很重要驶睦。

總結(jié)

asyncdefer 有一個(gè)共同點(diǎn):加載這樣的腳本都不會(huì)阻塞頁(yè)面的渲染。因此匿醒,用戶可以立即閱讀并了解頁(yè)面內(nèi)容场航。

但是,它們之間也存在一些本質(zhì)的區(qū)別:

順序 DOMContentLoaded
async 加載優(yōu)先順序廉羔。腳本在文檔中的順序不重要 —— 先加載完成的先執(zhí)行 不相關(guān)溉痢。可能在文檔加載完成前加載并執(zhí)行完畢憋他。如果腳本很小或者來(lái)自于緩存孩饼,同時(shí)文檔足夠長(zhǎng),就會(huì)發(fā)生這種情況竹挡。
defer 文檔順序(它們?cè)谖臋n中的順序) 在文檔加載和解析完成之后(如果需要镀娶,則會(huì)等待),即在 DOMContentLoaded 之前執(zhí)行揪罕。

在實(shí)際開(kāi)發(fā)中梯码,defer 用于需要整個(gè) DOM 的腳本,和/或腳本的相對(duì)執(zhí)行順序很重要的時(shí)候好啰。

async 用于獨(dú)立腳本轩娶,例如計(jì)數(shù)器或廣告,這些腳本的相對(duì)執(zhí)行順序無(wú)關(guān)緊要框往。

沒(méi)有腳本的頁(yè)面應(yīng)該也是可用的

請(qǐng)注意:如果你使用的是 deferasync鳄抒,那么用于將在腳本加載完成 之前 先看到頁(yè)面。

在這種情況下搅窿,某些圖形組件可能尚未初始化完成嘁酿。

因此,請(qǐng)記得添加一個(gè)“正在加載”的提示男应,并禁用尚不可用的按鈕。以讓用戶可以清楚地看到娱仔,他現(xiàn)在可以在頁(yè)面上做什么沐飘,以及還有什么是正在準(zhǔn)備中的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牲迫,一起剝皮案震驚了整個(gè)濱河市耐朴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盹憎,老刑警劉巖筛峭,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異陪每,居然都是意外死亡影晓,警方通過(guò)查閱死者的電腦和手機(jī)镰吵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挂签,“玉大人疤祭,你說(shuō)我怎么就攤上這事《牛” “怎么了勺馆?”我有些...
    開(kāi)封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)侨核。 經(jīng)常有香客問(wèn)我草穆,道長(zhǎng),這世上最難降的妖魔是什么搓译? 我笑而不...
    開(kāi)封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任续挟,我火速辦了婚禮,結(jié)果婚禮上侥衬,老公的妹妹穿的比我還像新娘诗祸。我一直安慰自己,他們只是感情好轴总,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布直颅。 她就那樣靜靜地躺著,像睡著了一般怀樟。 火紅的嫁衣襯著肌膚如雪功偿。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天往堡,我揣著相機(jī)與錄音械荷,去河邊找鬼。 笑死虑灰,一個(gè)胖子當(dāng)著我的面吹牛吨瞎,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播穆咐,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颤诀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了对湃?” 一聲冷哼從身側(cè)響起崖叫,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拍柒,沒(méi)想到半個(gè)月后心傀,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拆讯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年脂男,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了养叛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疆液,死狀恐怖一铅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情堕油,我是刑警寧澤潘飘,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站掉缺,受9級(jí)特大地震影響卜录,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜眶明,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一艰毒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧搜囱,春花似錦丑瞧、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至扮宠,卻和暖如春西乖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坛增。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工获雕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人收捣。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓届案,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親坏晦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萝玷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容