前言
在面試的時(shí)候,經(jīng)常會(huì)遇到一道經(jīng)典的面試題:
如何優(yōu)化網(wǎng)頁加載速度?
常規(guī)的回答中總會(huì)有一條:
把 css 文件放在頁面頂部,把 js 文件放在頁面底部。
那么,為什么要把 js 文件放在頁面的最底部呢?
我們先來看下這段代碼:
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Hi</title>
<script>
console.log("Howdy ~");
</script>
<script src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script>
<script src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script>
</head>
<body>
Hello ???? ~
</body>
</html>
他的執(zhí)行順序是:
- 在控制臺(tái)打?。?code>Howdy ~
- 請(qǐng)求并執(zhí)行?vue.global.js
- 請(qǐng)求并執(zhí)行?vue-router.global.js
- 在頁面中展示:
Hello ???? ~
- 觸發(fā)?DOMContentLoaded事件
瀏覽器的解析規(guī)則是:如果遇到?script
?標(biāo)簽,則暫停構(gòu)建?DOM
,轉(zhuǎn)而開始執(zhí)行?script
?標(biāo)簽,如果是,那么瀏覽器還需要一直等待其「下載」并「執(zhí)行」后,再繼續(xù)解析后面的 HTML。
如果請(qǐng)求并執(zhí)行「vue.global.js」需要 3 秒,「vue-router.global.js」需要 2 秒,那么頁面中的?Hello ???? ~
,則至少需要 5 秒以上才會(huì)展示出來。
可以看到,script 標(biāo)簽會(huì)阻塞瀏覽器解析 HTML,如果把?script
?都放在?head
?中,在網(wǎng)絡(luò)不佳的情況下,就會(huì)導(dǎo)致頁面長期處于白屏狀態(tài)。
在很久以前,一般都是將這些外聯(lián)腳本,放在?body
?標(biāo)簽的最后面,確保先解析展示?body
?中的內(nèi)容,然后再一個(gè)個(gè)請(qǐng)求執(zhí)行這些外聯(lián)腳本。
那有沒有其他更優(yōu)雅的解決方案呢?
答案是肯定的,現(xiàn)在?script
?標(biāo)簽新增了 2 個(gè)屬性:defer
?和?async
,就是為了解決此類問題,提升頁面性能的。
<script defer>
先看一下 MDN 上的解釋:?
這個(gè)布爾屬性被設(shè)定用來通知瀏覽器該腳本將在文檔完成解析后,觸發(fā) DOMContentLoaded 事件前執(zhí)行。
有 defer 屬性的腳本會(huì)阻止 DOMContentLoaded 事件,直到腳本被加載并且解析完成。
文檔是直接總結(jié)了他的特性,我們先看看下面的代碼,展開說說細(xì)節(jié),加深一下理解。
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Hi</title>
<script>
console.log("Howdy ~");
</script>
<script defer src="https://unpkg.com/vue@3.2.41/dist/vue.global.js"></script>
<script defer src="https://unpkg.com/vue-router@4.1.5/dist/vue-router.global.js"></script>
</head>
<body>
Hello ???? ~
</body>
</html>
他的執(zhí)行順序是:
- 在控制臺(tái)打?。?code>Howdy ~
- 在頁面中展示:
Hello ???? ~
- 請(qǐng)求并執(zhí)行?vue.global.js
- 請(qǐng)求并執(zhí)行?vue-router.global.js
- 觸發(fā)DOMContentLoaded事件
如果在?script
?標(biāo)簽上設(shè)置了?defer
?屬性,那么在瀏覽器解析到這里時(shí),會(huì)默默的在后臺(tái)開始下載此腳本,并繼續(xù)解析后面的 HTML,并不會(huì)阻塞解析操作。
等到 HTML 解析完成之后,瀏覽器會(huì)立即執(zhí)行后臺(tái)下載的腳本,腳本執(zhí)行完成之后,才會(huì)觸發(fā)?DOMContentLoaded
?事件。
看起來還是蠻好理解的吧?咱們?cè)賮碛懻?2 個(gè)小細(xì)節(jié):
Q1:?如果 HTML 解析完成之后,設(shè)置了?defer
?屬性的腳本還沒下載完成,會(huì)怎樣?
A1:?瀏覽器會(huì)等腳本下載完成之后,再執(zhí)行此腳本,執(zhí)行完成之后,再觸發(fā)?DOMContentLoaded
?事件。
Q2:?如果有多個(gè)設(shè)置了?defer
?屬性的腳本,那瀏覽器會(huì)如何處理?
A2:?瀏覽器會(huì)并行的在后臺(tái)下載這些腳本,等 HTML 解析完成,并且所有腳本下載完成之后,再按照他們?cè)?HTML 中出現(xiàn)的相對(duì)順序執(zhí)行,等所有腳本執(zhí)行完成之后,再觸發(fā)?DOMContentLoaded
?事件。
最佳實(shí)踐:
建議所有的外聯(lián)腳本都默認(rèn)設(shè)置此屬性,因?yàn)樗粫?huì)阻塞 HTML 解析,可以并行下載 JavaScript 資源,還可以按照他們?cè)?HTML 中的相對(duì)順序執(zhí)行,確保有依賴關(guān)系的腳本運(yùn)行時(shí),不會(huì)缺少依賴。
在 SPA 的應(yīng)用中,可以考慮把所有的?script
?標(biāo)簽加上?defer
?屬性,并且放到?body
?的最后面。在現(xiàn)代瀏覽器中,可以并行下載提升速度,也可以確保在老瀏覽器中,不阻塞瀏覽器解析 HTML,起到降級(jí)的作用。
注意:
-
defer
?屬性僅適用于外部腳本,如果?script
?腳本沒有?src
,則會(huì)忽略?defer
?特性。 -
defer
?屬性對(duì)模塊腳本(? ?<script type='module'></script>? ?)無效,因?yàn)槟K腳本就是以?defer
?的形式加載的。
<script async>
按照慣例,先看一下 MDN 上的解釋:
對(duì)于普通腳本,如果存在 async 屬性,那么普通腳本會(huì)被并行請(qǐng)求,并盡快解析和執(zhí)行。
對(duì)于模塊腳本,如果存在 async 屬性,那么腳本及其所有依賴都會(huì)在延緩隊(duì)列中執(zhí)行,因此它們會(huì)被并行請(qǐng)求,并盡快解析和執(zhí)行。
該屬性能夠消除解析阻塞的 Javascript。
解析阻塞的 Javascript 會(huì)導(dǎo)致瀏覽器必須加載并且執(zhí)行腳本,之后才能繼續(xù)解析。
感覺這段描述的已經(jīng)蠻清晰了,不過咱們還是先看看下面的代碼,展開說說細(xì)節(jié),加深一下理解。
?
<!DOCTYPE html>
<html lang="zh">
<head>
<title>Hi</title>
<script>
console.log("Howdy ~");
</script>
<script async src="https://google-analytics.com/analytics.js"></script>
<script async src="https://ads.google.cn/ad.js"></script>
</head>
<body>
Hello ???? ~
</body>
</html>
他的執(zhí)行順序是:
- 在控制臺(tái)打?。?code>Howdy ~
- 并行請(qǐng)求?analytics.js?和?ad.js
- 在頁面中展示:
Hello ???? ~
- 根據(jù)網(wǎng)絡(luò)的實(shí)際情況,以下幾項(xiàng)會(huì)無序執(zhí)行
- 執(zhí)行?analytics.js?(下載完后,立即執(zhí)行)
- 執(zhí)行?ad.js?(下載完后,立即執(zhí)行)
- 觸發(fā)? DOMContentLoaded ?事件(可能在在上面 2 個(gè)腳本之前,之間,之后觸發(fā))
瀏覽器在解析到帶有?async
?屬性的?script
?標(biāo)簽時(shí),也不會(huì)阻塞頁面,同樣是在后臺(tái)默默下載此腳本。當(dāng)他下載完后,瀏覽器會(huì)暫停解析 HTML,立馬執(zhí)行此腳本。
看起來還是蠻好理解的吧?咱們?cè)賮碛懻?2 個(gè)小細(xì)節(jié):
Q1:?如果設(shè)置了?async
?屬性的?script
?下載完之后,瀏覽器還沒解析完 HTML,會(huì)怎樣?
A1:?瀏覽器會(huì)暫停解析 HTML,立馬執(zhí)行此腳本,等執(zhí)行完之后,再繼續(xù)解析 HTML。
Q2:?如果有多個(gè)?async
?屬性的?script
?標(biāo)簽,那等他們下載完成之后,會(huì)按照代碼順序執(zhí)行嗎?
A2:?不會(huì)。執(zhí)行順序是:誰先下載完成,誰先執(zhí)行。async
?的特點(diǎn)是「完全獨(dú)立」,不依賴其他內(nèi)容。
最佳實(shí)踐:
當(dāng)我們的項(xiàng)目,需要集成其他獨(dú)立的第三方庫時(shí),可以使用此屬性,他們不依賴我們,我們也不依賴于他們。
通過設(shè)置此屬性,讓瀏覽器異步下載并執(zhí)行他,是個(gè)不錯(cuò)的優(yōu)化方案。
注意:
-
async
?特性僅適用于外部腳本,如果?script
?腳本沒有?src
,則會(huì)忽略?async
?特性。
總結(jié)
defer
- 不阻塞瀏覽器解析 HTML,等解析完 HTML 之后,才會(huì)執(zhí)行?
script
。 - 會(huì)并行下載 JavaScript 資源。
- 會(huì)按照 HTML 中的相對(duì)順序執(zhí)行腳本。
- 會(huì)在腳本下載并執(zhí)行完成之后,才會(huì)觸發(fā)?
DOMContentLoaded
?事件。 - 在腳本執(zhí)行過程中,一定可以獲取到 HTML 中已有的元素。
-
defer
?屬性對(duì)模板腳本無效。 - 適用于:所有外部腳本(通過?
src
?引用的?script
)。
async
- 不阻塞瀏覽器解析 HTML,但是?
script
?下載完成后,會(huì)立即中斷瀏覽器解析 HTML,并執(zhí)行此?script
。 - 會(huì)并行下載 JavaScript 資源。
- 互相獨(dú)立,誰先下載完,誰先執(zhí)行,沒有固定的先后順序,不可控。
- 由于沒有確定的執(zhí)行時(shí)機(jī),所以在腳本里面可能會(huì)獲取不到 HTML 中已有的元素。
-
DOMContentLoaded
?事件和?script
?腳本無相關(guān)性,無法確定他們的先后順序。 - 適用于:獨(dú)立的第三方腳本。
另外:async
?和?defer
?之間最大的區(qū)別在于它們的執(zhí)行時(shí)機(jī)。
One More Thing
你有沒有想過,如果一個(gè)?script
?標(biāo)簽同時(shí)設(shè)置?defer
?和?async
,瀏覽器會(huì)如何處理?
先說結(jié)論:從表現(xiàn)形式上來說,async
?的優(yōu)先級(jí)比?defer
?高,也就是如果同時(shí)存在這 2 個(gè)屬性,那么瀏覽器將會(huì)以?async
?的特性去加載此腳本。
這主要分 2 種情況:
如果是「普通腳本」,瀏覽器會(huì)優(yōu)先判斷async
屬性是否存在,如果存在,則以async
特性去加載此腳本,如果不存在,再去判斷是否存在defer
屬性。
如果是「模塊腳本」,瀏覽器會(huì)判斷async
屬性是否存在:
- 如果存在,瀏覽器會(huì)并行下載此模塊和他的所有依賴模塊,等全部下載完成之后,會(huì)立刻執(zhí)行此腳本。
- 如果不存在,瀏覽器也會(huì)并行下載此模塊和他的所有依賴模塊,然后等瀏覽器解析完 HTML 之后,再執(zhí)行此腳本。
- 另外需要注意的是:在模塊腳本上設(shè)置?
defer
?屬性是無效的。
一圖勝千言
最后,用一張圖概括一下這兩個(gè)屬性的加載模式吧:文章來源:http://www.zghlxwxcb.cn/news/detail-552288.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-552288.html
到了這里,關(guān)于玩轉(zhuǎn)代碼|Script 標(biāo)簽中的async與defer屬性詳細(xì)分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!