前言
defer 和async 属性 提供给开发者一个方式来告诉浏览器哪些脚本是需要异步加载的。
直接上图
Async
页面读取到脚本的时候开启异步加载线程,下载过程不阻塞DOM解析;
async 脚本在它们完成下载完成后的第一时间执行;
一旦脚本下载完成,如果DOM解析还没完成(DOMContentLoaded事件未触发),立即阻塞DOM解析,执行脚本;
DOMContentLoaded 可能在 async 之前或之后触发,不能保证谁先谁后。
多个async脚本不保证执行顺序。
Defer
defer 特性告诉浏览器不要等待脚本,浏览器将继续处理 HTML,构建 DOM。脚本会“在后台”下载,然后等 DOM 构建完成后,脚本才会执行。
页面读取到脚本的时候开启异步加载线程,下载过程不阻塞DOM解析;
DOM解析完成,DOMcontentLoaded事件还未触发时,执行defer脚本。DOMContentLoaded 事件处理程序等待具有 defer 特性的脚本执行完成。它仅在脚本下载且执行结束后才会被触发。
具有 defer 特性的脚本保持其相对顺序,就像常规脚本一样。,defer特性除了告诉浏览器“不要阻塞页面”之外,还可以确保脚本执行的相对顺序。因此,即使 before.js 先加载完成,它也需要等到 after.js 执行结束才会被执行。
defer和async都作用于外链js,如果 < script > 脚本没有 src,则会忽略 defer 特性
动态脚本
我们可以使用 JavaScript 动态地创建一个脚本,并将其附加(append)到文档(document)中:
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)
默认情况下,动态脚本的行为是“异步”的。
也就是说:
它们不会等待任何东西,也没有什么东西会等它们,先加载完成的脚本先执行,很像async。
如果我们显式地设置了 script.async=false,则可以改变这个规则。然后脚本将按照脚本在文档中的顺序执行,就像 defer 那样。
区别
应用场景
defer 用于需要整个 DOM 的脚本,或脚本的相对执行顺序很重要的时候。 如果你的脚本代码依赖于页面中的DOM元素(文档是否解析完毕),或者被其他脚本文件依赖。
例:
评论框
代码语法高亮
polyfill.js
实际开发中,我们可以将业务代码脚本加上 defer 属性,放到更上层的 head 标签下,这也是最新版 HtmlWebpackPlugin 插件的默认引入打包脚本的方式。
async 用于独立脚本,例如计数器或广告,这些脚本的相对执行顺序无关紧要。
例:
广告、网站流量分析脚本,百度统计之类的
如果不太能确定的话,用defer总是会比async稳定。。。
其他
render树
将 DOM 树和 CSS 合并成一棵渲染树,render 树在合适的时机会被渲染到页面中(比如遇到 script 时, 该 script 还没有下载到本地时)。
首次渲染
在 body 中第一个 script 资源下载完成之前,浏览器会进行首次渲染(如果只有dom,那么DOM 树构建完,页面首次渲染。),将该 script 标签前面的 DOM 树和 CSSOM(前提是之前的css已经加载完) 合并成一棵 Render 树,渲染到页面中。这是页面从白屏到首次渲染的时间节点,比较关键。
页面渲染
外链 css 不会影响 css 后面的 DOM 构建,但是会阻碍渲染。简单点说,外链 css 加载完之前,页面还是白屏。
外链 js 和外链 css 的顺序会影响页面渲染,这点尤为重要。当 body 中 js 之前的外链 css 未加载完之前,页面是不会被渲染的。
当body中 js 之前的 外链 css 加载完之后,js 之前的 DOM 树和 css 合并渲染树,页面渲染出该 js 之前的 DOM 结构。
DOMContentLoaded 事件
html文档解析完毕, 并且 html所引用的内联 js、以及外链 js 的同步代码都执行完毕后触发。
Load
当页面 DOM 结构中的 js、css、图片,以及 js 异步加载的 js、css 、图片都加载完成之后,才会触发 load 事件
video、audio、flash 不会影响 load 事件触发