【01-17】defer、async作用和區(qū)別分析

瀏覽器內(nèi)核可以分成兩部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎。早期渲染引擎和 JS 引擎并沒有十分明確的區(qū)分,但隨著 JS 引擎越來越獨立,內(nèi)核也成了渲染引擎的代稱(下文我們將沿用這種叫法)。渲染引擎又包括了 HTML 解釋器、CSS 解釋器、布局、網(wǎng)絡(luò)、存儲、圖形、音視頻、圖片解碼器等等零部件。

JS 引擎是獨立于渲染引擎存在的。我們的 JS 代碼在文檔的何處插入,就在何處執(zhí)行。當(dāng) HTML 解析器遇到一個 script 標(biāo)簽時,它會暫停渲染過程,將控制權(quán)交給 JS 引擎。JS 引擎對內(nèi)聯(lián)的 JS 代碼會直接執(zhí)行,對外部 JS 文件還要先獲取到腳本、再進(jìn)行執(zhí)行。等 JS 引擎運行完畢,瀏覽器又會把控制權(quán)還給渲染引擎,繼續(xù) CSSOM 和 DOM 的構(gòu)建。 因此與其說是 JS 把 CSS 和 HTML 阻塞了,不如說是 JS 引擎搶走了渲染引擎的控制權(quán)。

渲染引擎碰到j(luò)s就交出大權(quán)是因為他不知道js的內(nèi)容會不會對接下來的渲染有沒有影響。但是我們引入js的時候是知道有沒有影響的,可以根據(jù)具體情況用三種方式之一加載js。

三種加載方式

image.png

1.正常模式
<script src="script.js"></script>
沒有 defer 或 async,瀏覽器會立即加載并執(zhí)行指定的腳本,“立即”指的是在渲染該 script 標(biāo)簽之下的文檔元素之前,也就是說不等待后續(xù)載入的文檔元素,讀到就加載并執(zhí)行。

2.async模式
<script async src="script.js"></script>
有 async,script.js會被異步加載,即加載和渲染后續(xù)文檔元素的過程將和 script.js 的加載并行進(jìn)行(異步)。當(dāng) script.js加載完整立即執(zhí)行script.js。執(zhí)行script.js時,html解析暫停。
從加載完成立即執(zhí)行來看,async模式 執(zhí)行順序與寫的順序無關(guān),不保證執(zhí)行順序。

3.defer 模式
<script defer src="index.js"></script>
有 defer,script.js會被異步加載,即加載和渲染后續(xù)文檔元素的過程將和 script.js 的加載并行進(jìn)行(異步)。這一點與async模式一致。
不同的是當(dāng) script.js加載完成并不會立即執(zhí)行,而是在所有元素解析完成之后,DOMContentLoaded 事件觸發(fā)之前完成。因此它會按照寫的順序執(zhí)行。

// html 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>defer-async</title>

    <script type="text/javascript" async src='./async1.js'></script>
    <script type="text/javascript" async src='./async2.js'></script>
    <script type="text/javascript" src='./normal.js'></script>
</head>
<body>
    <div id="warp">warp</div>
</body>
</html>

然后 async1.js 文件巨大(到底有多大,我是把jquery的壓縮版拷進(jìn)來了),然后最后加上 console.log('async1');
文件async2.js 和 normal.js 中分別是 console.log('async2'); 和 console.log('normal');
打開網(wǎng)頁控制臺顯示如下: async2 先加載完成就先執(zhí)行了。


image.png
<script type="text/javascript" defer src='./defer1.js'></script>
<script type="text/javascript" defer src='./defer2.js'></script>
<script type="text/javascript" src='./normal.js'></script>

同理,defer1.js 里放了jquery的壓縮版源碼。defer2.js里只放了一句日志; 刷新網(wǎng)頁看下日志:
defer1 、defer2還是按照順序執(zhí)行的。

把a(bǔ)sync、defer都加上,

    <script type="text/javascript" async src='./async1.js'></script>
    <script type="text/javascript" async src='./async2.js'></script>

    <script type="text/javascript" defer src='./defer1.js'></script>
    <script type="text/javascript" defer src='./defer2.js'></script>
    <script type="text/javascript" src='./normal.js'></script>

日志如下:


image.png

這個順序應(yīng)該不是固定的,符合normal最早,defer1會在 defer2之前的規(guī)矩。 至于async 和 defer的前后則要看本身js的加載以及dom樹的構(gòu)建時機(jī)吧。

三種方式適合什么時候用

growingwiththeweb 推薦優(yōu)先級依次是 async defer normal。。

  • 當(dāng)你的js是個獨立的模塊且不依賴任何js,使用 async;
  • 如果你的js依賴其他js或者被其他js 依賴,使用 defer;
  • 如果你對js文件很小且被 async script 依賴,使用正常模式的script且放在async script 前面。

可能的坑

雖然理論上defer按加載順序執(zhí)行,但也有同學(xué)反映事實上并不是這樣。。比如這位同學(xué)的問題:


image.png

我認(rèn)為這是涉及到 event loop的 task和微任務(wù)了。
"在現(xiàn)實當(dāng)中,延遲腳本并不一定會按照順序執(zhí)行,也不一定會在 DOMContentLoaded 事件觸發(fā)前執(zhí)行,因此最好只包含一個延遲腳本。" 《JavaScript 高級程序設(shè)計(第三版)》如是說,所以腳本之間有依賴,最好使用一個異步腳本吧。比如上面同學(xué)那個問題 可以改成這樣<script src="1.js"></script>.

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

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

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