深度解析之異步加載(defer、async、module)和預(yù)加載(preload、prefetch、dns-prefetch、preconnect 、prerender)

廢話:異步加載和預(yù)加載一直都是前端優(yōu)化必備技能之一,今天我們就來(lái)深度解析一下常用的幾個(gè)關(guān)鍵點(diǎn)。

異步加載

廢話不多說(shuō),任何長(zhǎng)篇大論的教程都抵不過(guò)一張清晰明了的高清大圖來(lái)得好:

從這張圖里面,我們看到了什么,大概總結(jié)為以下四點(diǎn):

  • 默認(rèn)情況HTML解析,然后加載JS,此時(shí)HTML解析中斷,然后執(zhí)行JS,最后JS執(zhí)行完成恢復(fù)HTML解析。
  • defer情況下HTML和JS并駕齊驅(qū),最后才執(zhí)行JS
  • async情況則HTML和JS并駕齊驅(qū),JS的執(zhí)行可能在HTML解析之前就已經(jīng)完成了
  • 最后module情況和defer的情況類似,只不過(guò)會(huì)在提取的過(guò)程中加載多個(gè)JS文件罷了

好了,區(qū)分的大概基本已經(jīng)了解了,那怎么記住呢?默認(rèn)的情況我們已經(jīng)很熟了,就無(wú)需多記了。

defer翻譯過(guò)來(lái)是延緩的意思,也就是拖拖拉拉了,所以比較懶,也就是說(shuō)什么都不想做,也就是哪怕你把飯端在我面前,我也懶得動(dòng)嘴的那種,這么一想,我們不就記住了,哪怕你客戶端把JS文件下載好了,我也懶得執(zhí)行,最后實(shí)在是大家都干完事了,我才不情愿的去執(zhí)行JS文件。

async翻譯過(guò)來(lái)就是異步的意思,異步異步,不就是一步一步嘛,什么都想一步到位,也就是說(shuō),只要下載完我就立馬執(zhí)行,至于其他的想都不想。

module翻譯過(guò)來(lái)就是模塊的意思,es6用過(guò)的人基本都了解這個(gè)關(guān)鍵字,加載也和defer差不多,只不過(guò)可以加載多個(gè)JS文件而已。

我們?cè)賮?lái)看看這幾個(gè)加載的DOM事件時(shí)機(jī):

從這張圖可以看出大概這幾點(diǎn):

  • async 會(huì)在加載完JS后立即執(zhí)行,最遲也會(huì)在load事件前執(zhí)行完。
  • defer會(huì)在HTML解析完成后執(zhí)行,最遲也會(huì)在DOMContentLoaded事件前執(zhí)行完。

從上面我們可以看出,如果你的腳本依賴于DOM構(gòu)建完成是否完成,則可以使用defer;如果無(wú)需DOM的構(gòu)建,那就可以放心的使用async了。

defer

defer屬性僅適用于外部腳本,也就是僅當(dāng)存在src屬性時(shí)才會(huì)生效;如果一個(gè)script標(biāo)簽上面即存在defer屬性,也存在async屬性,那么瀏覽器會(huì)如何解析這種情況呢?我們通過(guò)一段代碼驗(yàn)證結(jié)果,詳情點(diǎn)擊這里

也就是說(shuō)defer的優(yōu)先級(jí)沒(méi)有async高,我們看一下規(guī)范是怎么處理這種情況的。

The defer attribute may be specified even if the async attribute is specified, to cause legacy Web browsers that only support defer (and not async) to fall back to the defer behavior instead of the blocking behavior that is the default.

規(guī)范只是說(shuō)明了在不支持async的情況下瀏覽器將會(huì)回退支持defer,但并沒(méi)有明確指明兩種都支持的這種情況,也就是說(shuō)這一種情況瀏覽器自行處理,經(jīng)過(guò)測(cè)試,各個(gè)瀏覽器表現(xiàn)行為:

  • Chrome瀏覽器表現(xiàn)為解析為async特性
  • Safari瀏覽器表現(xiàn)為async特性
  • Opera瀏覽器表現(xiàn)為async特性
  • Firefox瀏覽器表現(xiàn)為async特性

IE暫時(shí)沒(méi)有安裝,看來(lái)各大瀏覽器表現(xiàn)一致,總之a(chǎn)sync的優(yōu)先級(jí)是最高的。

兼容性

下面來(lái)看看defer的兼容性,移動(dòng)端一片大綠,可以放心使用,IE10以上可以放心使用,IE6-9有一點(diǎn)小問(wèn)題就是不會(huì)按照script標(biāo)簽的執(zhí)行順序進(jìn)行執(zhí)行,對(duì)于不依賴前后腳本庫(kù)的可以不用擔(dān)心,但是如果依賴庫(kù)的就不行了,比如你的項(xiàng)目依賴jQuery,后面緊接著使用jQuery的方法可能就會(huì)出現(xiàn)問(wèn)題。

async

和defer一樣,也僅僅適用于外部腳本,也就是僅當(dāng)存在src屬性時(shí)才會(huì)生效。

兼容性

async的兼容性在移動(dòng)端也是一片大綠,IE僅支持IE10+。

module

在現(xiàn)代瀏覽器中,我們可以聲明acript標(biāo)簽type=’module’屬性從而擁抱es6的模塊導(dǎo)入導(dǎo)出語(yǔ)法,就像這樣:

<script type="module">
  import { Max } from "./math.js";
  console.log(Max(1, 2, 7, 2, 0)); //7
</script>

看起來(lái)是不是令人很激動(dòng),似乎對(duì)于開(kāi)發(fā)者十分友好,但是這里也有幾個(gè)與傳統(tǒng)腳本不一樣的地方:

  • module默認(rèn)使用了”use strict”模式,這也意味著不能使用諸如arguments.callee這一類的語(yǔ)法。
  • 模塊只會(huì)加載一次,無(wú)論前后你寫(xiě)了多少次。
  • 不支持<!–const a = 1–>注釋。
  • module有自己的詞法作用域,比如定義一個(gè) var a = 1,并不會(huì)創(chuàng)建一個(gè)全局變量,因此你并不能通過(guò)window.a 訪問(wèn)到它的值。

模塊的導(dǎo)入方式目前僅支持以下幾種模式:

支持
import {math} from './math.mjs';
import {math} from '../math.mjs';
import {math} from '/modules/math.mjs';
import {math} from 'https://simple.example/modules/math.mjs';
//不支持
import {math} from 'jquery';

當(dāng)然,瀏覽器廠商也在考慮支持 import {math} from ‘jquery’ 這種格式,不過(guò),還是需要一段很長(zhǎng)的路要走。

module的默認(rèn)情況就是defer的,因此不必再module上面又添加一個(gè)defer熟悉,并且本身就不支持這種寫(xiě)法,但是支持async屬性,其加載渲染方式和async差不多,這里不再贅述。

兼容性

在移動(dòng)端的兼容性還算可以,但是IE貌似都敗下陣來(lái),只要edge16+以上還算支持,對(duì)于不支持module的瀏覽器可以使用nomodule屬性作為版本回退的方案解決。

最后來(lái)說(shuō)一下module的使用建議,大型項(xiàng)目(100模塊以上)不建議直接使用模塊語(yǔ)法,應(yīng)該使用打包工具諸如Webpack,Rollup,、或 Parcel,因?yàn)殪o態(tài)導(dǎo)入或?qū)С稣Z(yǔ)法是靜態(tài)可分析的,通過(guò)捆綁工具可以去掉多余的模塊,我們考慮下面這一種場(chǎng)景:

import { Modal } from './util.js';
Modal({
  title: 'hello'
})

如果我們通過(guò)打包工具打包這一份代碼,最終生成的JS文件將會(huì)只包含Modal這一個(gè)函數(shù),倘若我們沒(méi)有使用打包工具,瀏覽器將會(huì)下載整個(gè)util這一個(gè)JS文件,并通過(guò)進(jìn)一步分析了解了使用了Modal這一個(gè)函數(shù),這對(duì)于沒(méi)有用到util里面的全部函數(shù)的方式,則是一種多余的帶寬浪費(fèi)。

預(yù)加載

在我們的瀏覽器加載資源的時(shí)候,對(duì)于每一個(gè)資源都有其自身的默認(rèn)優(yōu)先級(jí),倘若我們能修改每一個(gè)資源的默認(rèn)優(yōu)先級(jí),那我們幾乎可以按照我們的預(yù)期加載想要加載的資源。

以谷歌瀏覽器為例,我們打開(kāi)控制臺(tái),并切換到Network選項(xiàng),點(diǎn)擊刷新頁(yè)面,在網(wǎng)絡(luò)下面的title一行點(diǎn)擊鼠標(biāo)右鍵,勾選Priority即可看到加載資源的優(yōu)先級(jí),我們可以看到樣式的級(jí)別比腳本的優(yōu)先級(jí)高,畢竟頁(yè)面的一加載進(jìn)來(lái)肯定是樣式首先需要渲染的,不然整個(gè)頁(yè)面便會(huì)四分五裂,用戶體驗(yàn)不好。

preload

preload翻譯過(guò)來(lái)就是預(yù)加載,一旦啟用后便會(huì)告知瀏覽器應(yīng)該盡快的加載某個(gè)資源,如果提取的資源3s內(nèi)未在當(dāng)前使用,在谷歌開(kāi)發(fā)工具將會(huì)觸發(fā)警告消息

大概的語(yǔ)法如下:

<link rel="preload" as="script" href="foo.js">
<link rel="preload" as="style" href="bar.css">

除了以上指定的資源外,還可以加載audio、font、video以及document等,詳情點(diǎn)擊這里了解。

跨域資源

如需加載跨域的資源列表,則需要正確設(shè)置CORS,接著便可以在<link>元素中設(shè)置好crossorigin屬性即可:

<link rel="preload" as="font" crossorigin="crossorigin" type="font/woff2" href="foo.woff2">

這里有一個(gè)特例便是無(wú)論是否跨域,字體的獲取都需要設(shè)置crossorigin屬性,這是由于歷史原因造成,有興趣了解可移步這里了解,另外我們還可以使用media響應(yīng)式的加載圖片,比如:

<link rel="preload" href="bg@2x.png" as="image" media="(max-width: 325px)">
  <link rel="preload" href="bg@3x.png" as="image" media="(min-width: 400px)">

另一個(gè)重要的地方便是如果預(yù)加載一個(gè)腳本,它并不是執(zhí)行:

//只拉取下載不執(zhí)行
var preloadLink = document.createElement("link");
preloadLink.href = "foo.js";
preloadLink.rel = "preload";
preloadLink.as = "script";
document.head.appendChild(preloadLink);

//如果需要執(zhí)行
var preloadedScript = document.createElement("script");
preloadedScript.src = "foo.js";
document.body.appendChild(preloadedScript);

兼容性

兼容似乎IE全體陣亡,edge也得17+才能勉強(qiáng)支持,火狐需要手動(dòng)啟動(dòng)支持,移動(dòng)端支持程度還是挺好的。

prefetch

簡(jiǎn)而言之預(yù)提取就是在我們頁(yè)面加載完成后,在帶寬可用的情況下,加載用戶下一步期待的頁(yè)面資源,比如企業(yè)認(rèn)證,一般都是分好幾個(gè)頁(yè)面進(jìn)行認(rèn)證的,在用戶從第一個(gè)頁(yè)面進(jìn)行認(rèn)證的時(shí)候,在頁(yè)面加載完成,用戶正在填寫(xiě)表單數(shù)據(jù)之時(shí),加載第二個(gè)頁(yè)面的部分資源,從而使用戶更快打開(kāi)下一個(gè)頁(yè)面,從而增加用戶體驗(yàn),示例:

<link rel="prefetch" href="demo.html">
<link rel="stylesheet" href="demo.css">

當(dāng)瀏覽器解析到link標(biāo)簽時(shí),讀取到rel的值為prefetch,便會(huì)將這一個(gè)資源添加的隊(duì)列中,當(dāng)瀏覽器空閑時(shí)便會(huì)預(yù)提取資源,但是在demo.html頁(yè)面中只是加載HTML,不會(huì)加載demo頁(yè)面里面的任何其他資源,除非你在demo頁(yè)面也明確使用了預(yù)提取。

兼容性

各大瀏覽器支持都還挺好,IE11+以上,但是Safari貌似到現(xiàn)在還沒(méi)支持。

image

dns-prefetch

我們都知道,當(dāng)我們?cè)跒g覽器的地址欄輸入域名的時(shí)候,首先要進(jìn)行的就是域名解析,因?yàn)槲覀冃枰虞d域名對(duì)應(yīng)的資源,這個(gè)過(guò)程很快,但是如果在移動(dòng)端,那可是一個(gè)分秒必爭(zhēng)的地方,當(dāng)一個(gè)頁(yè)面需要訪問(wèn)許多外部域名的資源的時(shí)候,如果我們能在用戶瀏覽頁(yè)面的時(shí)候,在瀏覽器空閑的時(shí)間,把可能需要訪問(wèn)的域名都提前做好了域名解析,那是不是大大增加了用戶打開(kāi)頁(yè)面的響應(yīng)時(shí)間,增加用戶體驗(yàn),為了解決這個(gè)問(wèn)題,w3c便提出來(lái)一個(gè)標(biāo)準(zhǔn),學(xué)名叫dns-prefetch。

dns-prefetch在淘寶上的使用

使用方法上面中已經(jīng)支持了,指定rel=”dns-prefetch”,在href中指定頁(yè)面需要解析的域名即可,你可能已經(jīng)注意到了上面的圖中域名使用了雙斜杠,這個(gè)雙斜杠表示URL以主機(jī)名開(kāi)頭,和你使用完整URL(比如http://g.alicdn.com/)是等效的。在RFC1808中被指定。

當(dāng)然并不是所有的頁(yè)面需要用到的外部域名都需要做這樣的域名解析,瀏覽器默認(rèn)會(huì)解析超鏈接屬性的href里面的域名,并且你的網(wǎng)站域名還不能是HTTPS,如果是HTTPS,則需要設(shè)置請(qǐng)求頭或加入一段強(qiáng)制開(kāi)啟域名解析的meta標(biāo)簽。

//HTTP
<link rel="dns-prefetch" > //多余
<a >
//HTTPS
<meta http-equiv="x-dns-prefetch-control" content="on">//強(qiáng)制開(kāi)啟
<a >

當(dāng)然,并不建議對(duì)HTTPS網(wǎng)站開(kāi)啟強(qiáng)制解析的方式,因?yàn)檫@樣會(huì)帶來(lái)一些安全隱患,具體可參考這里。

preconnect

預(yù)連接,也就是啟動(dòng)早期連接(包括DNS查找,TCP握手和可選TLS協(xié)商),我們來(lái)看一個(gè)例子:

<link  rel='preconnect' crossorigin>
<link  rel='stylesheet'>

一個(gè)網(wǎng)絡(luò)字體正常加載一般都包括:

  • 頁(yè)面加載樣式,解析樣式用到的網(wǎng)絡(luò)字體
  • 網(wǎng)絡(luò)字體開(kāi)始下載,首先開(kāi)始DNS的查找
  • 然后TCP握手
  • 如果是HTTPS,還有TLS協(xié)商,最后下載字體

差不多一個(gè)字體的渲染要經(jīng)過(guò)這么幾個(gè)過(guò)程,但是如果字體的前期準(zhǔn)備(DNS查找,TCP握手和可選TLS協(xié)商)和樣式的加載是并行執(zhí)行,是不是可以更快的渲染頁(yè)面,preconnect就是為這個(gè)而生的,從而優(yōu)化用戶體驗(yàn)。

當(dāng)然如果是跨域資源,不要忘了加上crossorigin屬性。

兼容性

IE15+以上部分兼容,移動(dòng)端兼容良好。

https://luoyelusheng.cn/wp-content/uploads/2019/03/image-9-1024x322.png

prerender

預(yù)渲染,簡(jiǎn)單來(lái)說(shuō)就是瀏覽器會(huì)下載指定鏈接的資源,并下載以及渲染它,就好比我們打開(kāi)了一個(gè)新的Tab標(biāo)簽頁(yè),靜默的在后臺(tái)的下載執(zhí)行,當(dāng)然,瀏覽器也不一定會(huì)下載渲染它,這取決預(yù)很多情況,比如瀏覽器是否空閑以及操作系統(tǒng)是否會(huì)放棄下載過(guò)慢的資源文件。

除非你真的能十分的肯定用戶接下來(lái)一定會(huì)觸發(fā)你所指定的資源地址,否則對(duì)于用戶來(lái)說(shuō)這是一種帶寬的浪費(fèi),使用例子如下:

<link rel="prerender" >

兼容性

雖然是prerender是HTML5規(guī)范的一部分,但是似乎很多廠商都還沒(méi)有實(shí)現(xiàn),但是IE11竟然支持。

結(jié)尾

講了這么多,最后整理了一個(gè)表格,幫助大家快速查閱參考,每個(gè)瀏覽器的實(shí)施細(xì)節(jié)都有所區(qū)別,這里以Chrome瀏覽器表格為例:

參考:
[1] https://www.w3.org/TR/resource-hints/#prerender
[2] https://dev.chromium.org/developers/design-documents/dns-prefetching
[3] 資源優(yōu)先級(jí) – 讓瀏覽器助您一臂之力
[4] JavaScript Loading Priorities in Chrome
[5] Chrome Resource Priorities and Scheduling
[6] Using JavaScript modules on the web
[7] https://www.w3.org/TR/html5/webappapis.html#module-script

原文出處:深度解析之異步加載(defer、async、module)和預(yù)加載(preload、prefetch、dns-prefetch、preconnect 、prerender)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容