现代浏览器总是并行加载资源。脚本文件互相不会阻塞,但是会阻塞其他资源(例如图片、字体等)的下载。
后来为了用户体验,有了 async
和 defer
,脚本标记为异步,不会阻塞其他线程解析和执行。
当 HTML 解析器被 JavaScript 脚本阻塞时,解析器虽然会停止构建 DOM,但仍会识别该脚本后面的资源,并进行预加载。
存在阻塞的 CSS 资源时,浏览器会延迟 JavaScript 的执行和 DOM 构建
<script>
标记时,DOM 构建将暂停,直至脚本完成执行。<script>
标签为止很重要。实际使用时,遵循下面两个原则:
defer
的 JavaScript 脚本文件不会停止 HTML 文档解析,而是等到解析结束才执行async
只能引用外部脚本,下载完马上执行,但是不能保证加载顺序。<script src="foo.js"></script>
浏览器会做如下处理
foo.js
foo.js
中的脚本<script src="foo.js" defer></script><script src="bar.js" defer></script>
defer
属性规定是否对异步加载的脚本延迟执行,直到页面加载为止。
foo.js
、bar.js
foo.js
和 bar.js
仍继续解析 documentDOMContentLoaded
事件前依次执行 foo.js
和 bar.js
<script src="foo.js" async></script><script src="bar.js" async></script>
async
属性规定异步加载脚本并且立即执行,则会异步执行。
foo.js
和 bar.js
DOMContentLoaded
事件前或者后async
:脚本相对于页面的其余部分异步地执行async
但使用 defer
:脚本将在页面完成解析时执行async
也不使用 defer
:在浏览器继续解析页面之前,立即读取并执行脚本如果 <script>
无 src
属性,则 defer
和 async
会被忽略
动态添加的 <script>
标签隐含 async
属性
图解脚本的异步加载
defer
会在 DOMContentLoaded 前依次执行async
则是下载完立即执行,不一定是在 DOMContentLoaded 前async
因为乱序,所以很适合像 Google Analytics 这样的无依赖脚本<link>
:加载外部 CSS 样式文件 。异步加载,继续解析 HTML。<script src='url'>
:加载 JavaScript 脚本文件,同步加载并阻塞解析 HTML,加载完马上执行。<script src='url' async>
:加载 JavaScript 脚本文件。异步加载,继续解析 HTML,加载完马上执行。<script src='url' defer>
:加载 JavaScript 脚本文件。异步加载,继续解析 HTML,加载完延迟执行。<img src='url' />
:加载图片,异步加载,继续解析 HTML;但是需要等待 CSS 解析完才解码,所以 CSS 阻塞图片呈现。DOMContentLoaded 标识着程序从同步脚本执行转化为事件驱动阶段。
<link>
标签放部头而 <script>
放 <body>
尾部,是因为 JavaScript 阻塞阻塞 DOM 树的构建<script>
且没有 defer
或 async
属性的标签时,会触发页面渲染,因而如果前面 CSS 资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本。