前言
回想起当年面试时面试官问我:你简历上说熟练HTML,那么说说script的async、defer的区别吧。当时一下就蒙了,心想工作中好像从来没有用到过啊,那次之后我细细查阅资料才彻底理解它们的作用和区别。
这算是很经典面试题了,这面试题目没有难度,但却可以体现一个前端的基本功水平,因此下面除了文字讲解外还会配合官方的图片和博主自定义的示例,便于更深刻理解。
先汇总一下在
HTML中的script会有这三种用法,然后下面一一说明。
- 默认按顺序执行用法:
<script src='https://...'></script>- 脚本下载完马上执行用法:
<script src='https://...' async></script>- 脚本下载完最后执行用法:
<script src='https://...' defer></script>
script
解释: 当浏览器正在执行解析 HTML 代码块时,如果遇到默认的 script 标签,就会阻塞从而暂停解析 HTML 的剩余代码,去加载该 javascript 脚本,脚本加载完成之后会立即执行脚本,最后继续解析 HTML 代码。
配合下图理解更直观。
再看看如下示例代码解释
<html lang="zh">
<head>
<script>
console.log("Hello log~");
</script>
<script src="https://.../Chart.min.js"></script>
<script src="https://.../moment.min.js"></script>
<script src="https://.../vue.min.js"></script>
</head>
<body>
您好,我是天天鸭的示例1
</body>
</html>
执行顺序会如下:
- 执行打印:
console.log("Hello log~");- 下载并执行
Chart外部脚本:Chart.min.js- 下载并执行
moment外部脚本:moment.min.js- 下载并执行
vue外部脚本:vue.min.js- 显示文本内容:
您好,我是天天鸭的示例1
注意: 默认script会按顺序执行脚本,如果中间有一个脚本执行时间长, 后续的脚本就会排队,HTML也停止解析等待执行完成,这时会有白屏情况出现。
script async
解释: 当浏览器正在执行解析 HTML 代码块时,如果遇到 script 标签并且标识是async,不会阻塞解析 HTML
的剩余代码, 而是异步请求去加载该 javascript 脚本,但脚本加载完成之后会立即执行脚本并且此时会阻塞停止解析HTML,最后执行完脚本继续解析 HTML 代码。
配合下图理解更直观。
再看看如下示例代码解释
<html lang="zh">
<head>
<script>
console.log("Hello log~");
</script>
<script async src="https://.../Chart.min.js" ></script>
<script async src="https://.../moment.min.js"></script>
<script async src="https://.../vue.min.js"></script>
</head>
<body>
您好,我是天天鸭的示例2
</body>
</html>
执行顺序会如下:
- 执行打印:
console.log("Hello log~");- 异步下载
Chart.min.js、moment.min.js和vue.min.js脚本,此时HTML正常解析中- 下载完开始脚本,此时阻塞HTML解析
- 最后接着解析剩下
HTML
注意: 带async标识的script会在执行脚本时阻塞HTML解析,但下载脚本时不会,所以如果页面HTML结构比较简单那么会在脚本下载过程中就已经执行完成,因此上述例子是屏幕先显示 您好,我是天天鸭的示例2,再执行脚本。
如果HTML结构复杂且量大解析时间较长,会在脚本执行时中断,执行完成接着解析。
script defer
解释: 当浏览器正在执行解析 HTML 代码块时,如果遇到有 script 标签并且标识是defer,不会阻塞解析 HTML 的剩余代码, 而是异步请求去加载该 javascript 脚本,但脚本加载完成之后并不会立即执行脚本,直到最后解析完 HTML 代码,最后再去执行脚本。
配合下图理解更直观。
再看看如下示例代码解释
<html lang="zh">
<head>
<script>
console.log("Hello log~");
</script>
<script defer src="https://.../Chart.min.js" ></script>
<script defer src="https://.../moment.min.js"></script>
<script defer src="https://.../vue.min.js"></script>
</head>
<body>
您好,我是天天鸭的示例3
</body>
</html>
执行顺序会如下:
- 执行打印:
console.log("Hello log~");- 异步下载
Chart.min.js、moment.min.js和vue.min.js脚本,此时HTML正常解析中- 下载完脚本后不会立即执行,等待
HTML全部解析完显示文本内容:您好,我是天天鸭的示例3- 最后执行脚本
注意: 如果多个defer脚本,浏览器会并行下载,无论下载速度的快慢都不影响执行的顺序,会按HTML中出现的顺序依次执行。
混合使用
真实场景可以会几种方法混用,示例如下。
<html lang="zh">
<head>
<script>
console.log("Hello log~");
</script>
<script defer src="https://.../Chart.min.js" ></script>
<script async src="https://.../moment.min.js"></script>
<script defer src="https://.../vue.min.js"></script>
</head>
<body>
您好,我是天天鸭的示例4
</body>
</html>
执行顺序会如下:
- 执行打印:
console.log("Hello log~");- 异步下载
Chart.min.js、moment.min.js和vue.min.js脚本- 因为HTML比较简单,脚本下载过程中就执行完成了,显示文本内容:您好,我是天天鸭的示例4。
moment.min.js脚本下载完成后马上执行Chart.min.js和vue.min.js会在HTML解析完成后执行
小结
- 原生
script阻塞HTML的解析,按顺序执行 defer不阻塞HTML的解析,并且脚本按HTML的顺序执行,HTML解析完才执行脚本async有可能阻塞HTML的解析,脚本按网络请求顺序执行,谁先下载完就先执行谁,不可控
因此,理论上如果脚本独立的与其它资源加载没有关联可以选择async(可能会阻塞), 但脚本之间存在依赖关系需要选择defer
注意: 如果同时存在async,defer属性,async优级更高
好啦先写到这时,如果那里写的不好或者不对,欢迎指出。