你不知道的 script 標簽的 defer 與 async 屬性

我持續組織了近一年的源碼共讀活動,感興趣的可以?點此掃碼加我微信?ruochuan12?參與,每周大家一起學習200行左右的源碼,共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》?包含20余篇源碼文章。歷史面試系列。另外:目前建有江西|湖南|湖北籍前端群,可加我微信進群。


fa1873dbb5badcd595dcf274f00e9124.png

前言

在面試的時候,經常會遇到一道經典的面試題:

如何優化網頁加載速度?

常規的回答中總會有一條:

把 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>

他的執行順序是:

  • 在控制臺打印:Howdy ~

  • 請求并執行 vue.global.js

  • 請求并執行 vue-router.global.js

  • 在頁面中展示:Hello 👋🏻 ~

  • 觸發 DOMContentLoaded[1] 事件

cadebcbd18226ef32c82c2a5c03ad1ae.png
script 加載邏輯

瀏覽器的解析規則是:如果遇到 script 標簽,則暫停構建 DOM,轉而開始執行 script 標簽,如果是外部 script[2],那么瀏覽器還需要一直等待其「下載」并「執行」后,再繼續解析后面的 HTML。

如果請求并執行「vue.global.js」需要 3 秒,「vue-router.global.js」需要 2 秒,那么頁面中的 Hello 👋🏻 ~,則至少需要 5 秒以上才會展示出來。

可以看到,script 標簽會阻塞瀏覽器解析 HTML,如果把 script 都放在 head 中,在網絡不佳的情況下,就會導致頁面長期處于白屏狀態。

在很久以前,一般都是將這些外聯腳本,放在 body 標簽的最后面,確保先解析展示 body ?中的內容,然后再一個個請求執行這些外聯腳本。

那有沒有其他更優雅的解決方案呢?

答案是肯定的,現在 script ?標簽新增了 2 個屬性:deferasync,就是為了解決此類問題,提升頁面性能的。

<script defer>

先看一下 MDN 上的解釋:

這個布爾屬性被設定用來通知瀏覽器該腳本將在文檔完成解析后,觸發 DOMContentLoaded 事件前執行。

有 defer 屬性的腳本會阻止 DOMContentLoaded 事件,直到腳本被加載并且解析完成。

文檔是直接總結了他的特性,我們先看看下面的代碼,展開說說細節,加深一下理解。

<!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>

他的執行順序是:

  • 在控制臺打印:Howdy ~

  • 在頁面中展示:Hello 👋🏻 ~

  • 請求并執行 vue.global.js

  • 請求并執行 vue-router.global.js

  • 觸發 DOMContentLoaded[3] 事件

a27701709cdec0e92f097c0d21305d1b.png
script defer 加載邏輯

如果在 script 標簽上設置了 defer 屬性,那么在瀏覽器解析到這里時,會默默的在后臺開始下載此腳本,并繼續解析后面的 HTML,并不會阻塞解析操作。

等到 HTML 解析完成之后,瀏覽器會立即執行后臺下載的腳本,腳本執行完成之后,才會觸發 DOMContentLoaded 事件。

看起來還是蠻好理解的吧?咱們再來討論 2 個小細節:

Q1: 如果 HTML 解析完成之后,設置了 defer 屬性的腳本還沒下載完成,會怎樣?

A1: 瀏覽器會等腳本下載完成之后,再執行此腳本,執行完成之后,再觸發 DOMContentLoaded 事件。

Q2: 如果有多個設置了 defer 屬性的腳本,那瀏覽器會如何處理?

A2: 瀏覽器會并行的在后臺下載這些腳本,等 HTML 解析完成,并且所有腳本下載完成之后,再按照他們在 HTML 中出現的相對順序執行,等所有腳本執行完成之后,再觸發 DOMContentLoaded 事件。

最佳實踐:

建議所有的外聯腳本都默認設置此屬性,因為他不會阻塞 HTML 解析,可以并行下載 JavaScript 資源,還可以按照他們在 HTML 中的相對順序執行,確保有依賴關系的腳本運行時,不會缺少依賴。

在 SPA 的應用中,可以考慮把所有的 script 標簽加上 defer 屬性,并且放到 body 的最后面。在現代瀏覽器中,可以并行下載提升速度,也可以確保在老瀏覽器中,不阻塞瀏覽器解析 HTML,起到降級的作用。

注意:

  • defer 屬性僅適用于外部腳本,如果 script 腳本沒有 src,則會忽略 defer 特性。

  • defer 屬性對模塊腳本(script type='module'[4])無效,因為模塊腳本就是以 defer 的形式加載的。

<script async>

按照慣例,先看一下 MDN 上的解釋:

對于普通腳本,如果存在 async 屬性,那么普通腳本會被并行請求,并盡快解析和執行。

對于模塊腳本,如果存在 async 屬性,那么腳本及其所有依賴都會在延緩隊列中執行,因此它們會被并行請求,并盡快解析和執行。

該屬性能夠消除解析阻塞的 Javascript。

解析阻塞的 Javascript 會導致瀏覽器必須加載并且執行腳本,之后才能繼續解析。

感覺這段描述的已經蠻清晰了,不過咱們還是先看看下面的代碼,展開說說細節,加深一下理解。

<!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>

他的執行順序是:

  • 在控制臺打印:Howdy ~

  • 并行請求 analytics.jsad.js

  • 在頁面中展示:Hello 👋🏻 ~

  • 根據網絡的實際情況,以下幾項會無序執行

    • 執行 analytics.js(下載完后,立即執行)

    • 執行 ad.js(下載完后,立即執行)

    • 觸發 DOMContentLoaded 事件(可能在在上面 2 個腳本之前,之間,之后觸發)

a4e8456dfeac467f77ab975718c7d7b3.png
script async 加載邏輯

瀏覽器在解析到帶有 async 屬性的 script 標簽時,也不會阻塞頁面,同樣是在后臺默默下載此腳本。當他下載完后,瀏覽器會暫停解析 HTML,立馬執行此腳本。

看起來還是蠻好理解的吧?咱們再來討論 2 個小細節:

Q1:如果設置了 async 屬性的 script 下載完之后,瀏覽器還沒解析完 HTML,會怎樣?

A1:瀏覽器會暫停解析 HTML,立馬執行此腳本,等執行完之后,再繼續解析 HTML。

Q2:如果有多個 async 屬性的 script 標簽,那等他們下載完成之后,會按照代碼順序執行嗎?

A2:不會。執行順序是:誰先下載完成,誰先執行。async 的特點是「完全獨立」,不依賴其他內容。

最佳實踐:

當我們的項目,需要集成其他獨立的第三方庫時,可以使用此屬性,他們不依賴我們,我們也不依賴于他們。通過設置此屬性,讓瀏覽器異步下載并執行他,是個不錯的優化方案。

注意:

  • async 特性僅適用于外部腳本,如果 script 腳本沒有 src,則會忽略 async 特性。

總結

defer

  • 不阻塞瀏覽器解析 HTML,等解析完 HTML 之后,才會執行 script

  • 會并行下載 JavaScript 資源。

  • 會按照 HTML 中的相對順序執行腳本。

  • 會在腳本下載并執行完成之后,才會觸發 DOMContentLoaded 事件。

  • 在腳本執行過程中,一定可以獲取到 HTML 中已有的元素。

  • defer 屬性對模塊腳本無效。

  • 適用于:所有外部腳本(通過 src 引用的 script)。

async

  • 不阻塞瀏覽器解析 HTML,但是 script 下載完成后,會立即中斷瀏覽器解析 HTML,并執行此 script

  • 會并行下載 JavaScript 資源。

  • 互相獨立,誰先下載完,誰先執行,沒有固定的先后順序,不可控。

  • 由于沒有確定的執行時機,所以在腳本里面可能會獲取不到 HTML 中已有的元素。

  • DOMContentLoaded 事件和 script 腳本無相關性,無法確定他們的先后順序。

  • 適用于:獨立的第三方腳本。

另外:asyncdefer 之間最大的區別在于它們的執行時機。

One More Thing

你有沒有想過,如果一個 script 標簽同時設置 deferasync,瀏覽器會如何處理?

先說結論:從表現形式上來說,async 的優先級比 defer 高,也就是如果同時存在這 2 個屬性,那么瀏覽器將會以 async 的特性去加載此腳本。

這主要分 2 種情況:

如果是「普通腳本」,瀏覽器會優先判斷async屬性是否存在,如果存在,則以async特性去加載此腳本,如果不存在,再去判斷是否存在defer屬性。

如果是「模塊腳本[5]」,瀏覽器會判斷async屬性是否存在:

  • 如果存在,瀏覽器會并行下載此模塊和他的所有依賴模塊,等全部下載完成之后,會立刻執行此腳本。

  • 如果不存在,瀏覽器也會并行下載此模塊和他的所有依賴模塊,然后等瀏覽器解析完 HTML 之后,再執行此腳本。

  • 另外需要注意的是:在模塊腳本上設置 defer 屬性是無效的。

一圖勝千言

最后,用一張圖概括一下這兩個屬性的加載模式吧:

9766f81576b2fba19282cb015abe06ce.png
defer 和 async 的加載模式

思考題 🤔

  • 為什么瀏覽器在解析到普通的 script 標簽時,必須先執行他?

  • 普通的 script 標簽會阻塞瀏覽器解析 HTML,這會導致什么問題?

本文首發于:https://github.com/mrlmx/blogs/issues/4 ,如果喜歡,記得去點個贊哦~ 👍 ??

參考

  • https://javascript.info/script-async-defer[6]

  • https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html[7]

  • https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script[8]

  • https://html.spec.whatwg.org/multipage/scripting.html[9]

相關鏈接

[1]

DOMContentLoaded: https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event

[2]

外部 script: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-src

[3]

DOMContentLoaded: https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event

[4]

script type='module': https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type

[5]

模塊腳本: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type

[6]

https://javascript.info/script-async-defer: https://javascript.info/script-async-defer

[7]

https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html: https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html

[8]

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script: https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script

[9]

https://html.spec.whatwg.org/multipage/scripting.html: https://html.spec.whatwg.org/multipage/scripting.html


8960acaa37cc839ecd12222ae60f4980.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列》20余篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會寫一篇年度總結,已經堅持寫了8年,點擊查看年度總結。
同時,最近組織了源碼共讀活動,幫助5000+前端人學會看源碼。公眾號愿景:幫助5年內前端人走向前列。

596f71fba241b4a4f710904208666a46.jpeg

掃碼加我微信 lxchuan12、拉你進源碼共讀

今日話題

目前建有江西|湖南|湖北?籍 前端群,想進群的可以加我微信 lxchuan12?進群。分享、收藏、點贊、在看我的文章就是對我最大的支持~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274505.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274505.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274505.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

我是怎么調試 Element UI 源碼的

我持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試系列。另外&#xff1a;目前建…

java對象的理解

1、看生成幾個對象&#xff0c;就看new了幾次&#xff0c; Stu s1new Stu(“張三”); Stu s2s1;-這里也是生成了一個對象&#xff0c;只不過s1和s2指向了同一個對象 2、Stu s1new Stu(“張三”); Stu s2 new Stu(“李四”); s1s2&#xff1b;這里生成了兩個對象&#xff0c;但是…

模板緩沖_模板緩沖以及如何使用它可視化體積相交

模板緩沖介紹 (Introduction) The trendy thing in real-time rendering these days is ray-tracing. However, traditional rasterization hasn’t disappeared, and it won’t in the near future. I recommend this blog post on the subject: A hybrid rendering pipeline …

重磅!哈啰 Quark Design 正式開源,下一代跨技術棧前端組件庫

大家好&#xff0c;我是若川。我持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

對lua協程的一點理解

讀《Programming In Lua》協程那一章&#xff0c;比較困惑的還是procuer-consumer那個例子&#xff1a; function consumer(prod)while true dolocal x receive(prod)print(x)end endfunction receive(prod)local status, value coroutine.resume(prod)return value endfunct…

b端 ux 設計思維_借助系統思維從視覺設計過渡到UX

b端 ux 設計思維“How can I switch to UX?” This is a common question from visual designers because there’s a lot of overlap on the surface. But it can also be a difficult transition since UX encompasses much more below the surface.“如何切換到UX&#xff…

三面面試官:運行 npm run xxx 的時候發生了什么?

大家好&#xff0c;我是若川。近期發現好些小伙伴工作有2-3年了&#xff0c;基本不會寫腳手架&#xff0c;或者說沒學過腳手架。對腳手架大致是如何執行的基本不太知道。其實這類學習資料真的挺多的。而且我們基本天天 npm run dev&#xff0c;應該學習內部實現。不知道的小伙伴…

figma下載_Figma的自動版式實用

figma下載Figma’s Auto Layout has been around for a while, but not everyone’s aware of the benefits it brings. It doesn’t replace constraints, they’re still very much needed. The trick is to use the right feature where necessary. I want to show you how …

Qt通過ODBC讀取excel文件

之前替學校考試科用C Builder做過一個小的數據庫工具&#xff0c;處理excel表格用的&#xff0c;現在想轉換到Qt平臺下來&#xff0c;在網上搜了搜有一些利用OBDC讀取xls文件的教程&#xff1a; http://hi.baidu.com/kxw102/item/770c496d5736470ca0cf0f1d http://blog.sina.co…

真 · 三面面試官:運行 npm run xxx 的時候發生了什么?

昨晚沒權限我只放了鏈接&#xff0c;今天聯系開了白名單。昨天推文主要是為了投票&#xff0c;表明 Node.js 的重要性&#xff0c;有人評論是水文。今天重新轉載下。歡迎繼續點此去投票。投票顯示有高達近80% 表示不太會開發腳手架&#xff0c;看來大多數人確實沒有應用場景。可…

ovo svm_反思我在OVO擔任遠程產品設計實習生的時間

ovo svmIn a quiet bedroom accompanied only by the low humming of my laptop fan, I sat before a Google Hangouts meeting, and got to know my colleagues for the first time, unaware of the joy of a ride that was waiting for me at OVO Design.在一個安靜的臥室里&…

native的Socket向Android的LocalSocketServer發送漢字亂碼的問題

native的Socket發送字節流默認是GB2312的&#xff0c;所以在Java方面需要指定GB2312 byte[] buffer new byte[50]; StringBuffer strBuf new StringBuffer(); InputStream input receiver.getInputStream(); while((len input.read(buffer)) ! -1) {String newStr new Str…

最受讀者喜愛的前端書 Top 15【留言送書】

最受讀者喜愛的前端書Top 15JavaScript高級程序設計&#xff08;第4版&#xff09;| 中文版累計銷量32萬冊&#xff0c;JavaScript“紅寶書”全新升級 | 涵蓋ECMAScript 2019&#xff0c;全面深入&#xff0c;入門和進階俱佳 | 結合視頻講解配套編程環境&#xff0c;助你輕松掌…

圖文結合簡單易學的 npm 包的發布流程

大家好&#xff0c;我是若川。我持續組織了近一年的源碼共讀活動&#xff0c;感興趣的可以 點此掃碼加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。歷史面試…

擬態防御_擬態從未消失。 這就是為什么。

擬態防御Looking back on design languages, what Apple’s WWDC 2020 Keynote means for the future of design languages, and how we move on from here.回顧設計語言&#xff0c;Apple的WWDC 2020主題演講對設計語言的未來意味著什么&#xff0c;以及我們如何從這里繼續前進…

C++二維數組做形參

二位數組作為形參&#xff0c;目前僅知道兩種形式&#xff0c;一種直接采用二維數組&#xff0c;一種是用二維指針。以下是做的一個簡單的實例。 大家幫我看看&#xff0c;在實際應用中兩者有和優缺點。當然&#xff0c;有更好的方式更好了。 以下均應用在字符串數組中 void ar…

經常開發后臺管理系統,如何提升自己?推薦~【留言送書】

大家好&#xff0c;我是若川。之前送過N次書&#xff0c;可以點此查看回饋粉絲&#xff0c;現在又和博文視點合作再次爭取了幾本書&#xff0c;具體送書規則看文末。Vue.js是一套用于構建用戶界面的漸進式框架。與其他大型框架不同的是&#xff0c;它可以自底向上逐層應用。Vue…

lottie 動畫_使用After Effects和Lottie制作網絡動畫而不會損失質量

lottie 動畫A quick getting started guide快速入門指南 I recently took on a project where the team wanted to add some animated icons and a logo. Besides UX & UI design I am also a motion graphic designer so I took on the challenge of doing it with after…

如何編輯ttf字體文件

libfreetype的目標是以最小的內存最快的速度&#xff0c;讀取和渲染字體。因此libfreetype并不適宜用來編輯ttf字體文件。編輯字體文件&#xff0c;可以用FontCreator、微軟fonttools、fontforge&#xff08;蘋果有個osxfonttools&#xff0c;這里不討論&#xff09;FontCreato…

最優秀的技術能力,是技術領導力!

最近和幾個剛晉升為技術經理的朋友們約飯&#xff0c;席間互相吐槽職場中的喜怒哀樂&#xff1a; “開始帶團隊&#xff0c;既擔心自己長時間不寫代碼技術功底退化&#xff0c;又怕手下人干不好&#xff0c;該怎么辦&#xff1f;”“我都想回去敲代碼了&#xff0c;拼命熬到管理…