拒絕js阻塞—defer、async做用和區別分析

js阻塞原理

瀏覽器內核能夠分紅兩部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎。早期渲染引擎和 JS 引擎並無十分明確的區分,但隨着 JS 引擎愈來愈獨立,內核也成了渲染引擎的代稱(下文咱們將沿用這種叫法)。渲染引擎又包括了 HTML 解釋器、CSS 解釋器、佈局、網絡、存儲、圖形、音視頻、圖片解碼器等等零部件。 javascript

JS 引擎是獨立於渲染引擎存在的。咱們的 JS 代碼在文檔的何處插入,就在何處執行。當 HTML 解析器遇到一個 script 標籤時,它會暫停渲染過程,將控制權交給 JS 引擎。JS 引擎對內聯的 JS 代碼會直接執行,對外部 JS 文件還要先獲取到腳本、再進行執行。等 JS 引擎運行完畢,瀏覽器又會把控制權還給渲染引擎,繼續 CSSOM 和 DOM 的構建。 所以與其說是 JS 把 CSS 和 HTML 阻塞了,不如說是 JS 引擎搶走了渲染引擎的控制權。 html

渲染引擎碰到js就交出大權是由於他不知道js的內容會不會對接下來的渲染有沒有影響。可是咱們引入js的時候是知道有沒有影響的,能夠根據具體狀況用三種方式之一加載js。java

JS的三種加載方式

js 有三種加載方式。jquery

  1. 正常模式
<script src="script.js"></script>

沒有 defer 或 async,瀏覽器會當即加載並執行指定的腳本,「當即」指的是在渲染該 script 標籤之下的文檔元素以前,也就是說不等待後續載入的文檔元素,讀到就加載並執行。web

  1. async模式
<script async src="script.js"></script>

有 async,script.js會被異步加載,即加載和渲染後續文檔元素的過程將和 script.js 的加載並行進行(異步)。當 script.js加載完整當即執行script.js。執行script.js時,html解析暫停。
從加載完成當即執行來看,async模式 執行順序與寫的順序無關,不保證執行順序。segmentfault

  1. defer 模式
<script defer src="index.js"></script>

有 defer,script.js會被異步加載,即加載和渲染後續文檔元素的過程將和 script.js 的加載並行進行(異步)。這一點與async模式一致。
不一樣的是當 script.js加載完成並不會當即執行,而是在全部元素解析完成以後,DOMContentLoaded 事件觸發以前完成。所以它會按照寫的順序執行。瀏覽器

三種方式的直觀對比

一圖勝千言: 原圖地址 網絡

來個demo

// 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的壓縮版拷進來了),而後最後加上 console.log('async1');
文件async2.jsnormal.js 中分別是 console.log('async2');console.log('normal');
打開網頁控制檯顯示以下: async2 先加載完成就先執行了。
dom

而當我把 前面引用換成defer時異步

<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裏只放了一句日誌; 刷新網頁看下日誌:

defer1 、defer2仍是按照順序執行的。

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

日誌以下:

這個順序應該不是固定的,符合normal最先,defer1會在 defer2以前的規矩。 至於async 和 defer的先後則要看自己js的加載以及dom樹的構建時機吧。

三種方式適合何時用

growingwiththeweb 推薦優先級依次是 async defer normal。。

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

可能的坑

雖然理論上defer按加載順序執行,但也有同窗反映事實上並非這樣。。好比這位同窗的問題:

我認爲這是涉及到 event loop的 task和微任務了。
"在現實當中,延遲腳本並不必定會按照順序執行,也不必定會在 DOMContentLoaded 事件觸發前執行,所以最好只包含一個延遲腳本。" 《JavaScript 高級程序設計(第三版)》如是說,因此腳本之間有依賴,最好使用一個異步腳本吧。好比上面同窗那個問題 能夠改爲這樣<script src="1.js"></script>.

參考資料

掘金小冊
async vs defer
談談script標籤
defer async區別

相關文章
相關標籤/搜索