脚本加载方式
参考文章:script 的三种加载模式:默认加载、defer、async (opens new window)
script 有三种常见加载模式:
- 默认加载:会阻塞 DOM 构建
- defer 加载:下载照旧,但执行延后
- async:下载完就立即执行,适合没有依赖的脚本
defer是等构建完dom树再执行,async是下载完就立即执行。两者在下载时都不会阻断dom树构建,而默认模式下载会阻断dom构建。所以依赖dom的适合用defer,不依赖dom的可以用async
# 默认加载
- 如果你在文档的
<head>
部分或者文档底部的<body>
部分使用<script>
标签,而且没有设置async
或defer
属性,脚本将同步加载。 - 在这种情况下,浏览器会立即开始下载脚本,并且在脚本下载完成前阻塞 HTML 解析和渲染。这可能导致页面加载速度变慢,因为浏览器必须等待脚本下载和执行完成后才能继续加载和渲染其他部分。
<script src="path/to/your/script.js"></script>
这种写法有一个问题:它会 阻塞 HTML 的 DOM 构建。
假如我们在 head 元素中使用了 script 脚本,它就会阻止后面元素的渲染,包括 body 元素,此时执行document.querySeletor('body')
拿到的是 null。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<script>
// 拿到 null
console.log(document.querySeletor('body'));
</script>
</head>
<body></body>
</html>
2
3
4
5
6
7
8
9
10
11
12
此外,当脚本足够大,加载执行足够久时,会导致页面长时间没能渲染出完整页面。
这也是我们将业务代码脚本放到 body 最下边的原因,这样能确保脚本能够访问一个完整的 DOM 树,也不会阻止页面的渲染。
缺点是,HTML 很长的时候,解析到脚本就会花上一点时间,然后才会请求对应的脚本资源。
不过通常来说,HTML 内容都比较简单,二者感受不到太大区别,除非你网很卡。
# 异步加载
- 如果你在
<script>
标签上添加了async
属性,脚本将异步加载。 - 异步加载的脚本会在下载完成后立即执行,而不会阻塞 HTML 解析和渲染。这对于不影响页面展示的脚本(如统计代码)很有用。
<script async src="app.js"></script>
async,“异步” 之意。同样对内嵌脚本无效。
设置 async 后,脚本一旦被下载好了就会执行,不管什么时机。
适合与执行顺序无关的脚本,比如广告、网站流量分析脚本。
比如插入 Google 分析脚本:
<script async src="//www.google-analytics.com/analytics.js"></script>
# 延迟加载
- 如果你在
<script>
标签上添加了defer
属性,脚本也会异步加载。 - 与
async
不同的是,带有defer
属性的脚本会在 HTML 解析完成后、DOMContentLoaded
事件触发前执行。多个带有defer
属性的脚本会按照它们在文档中出现的顺序执行。
<script defer src="app.js"></script>
defer,“延迟” 之意。这里的延迟,指的是延迟执行脚本,下载则不会被阻塞。
需要注意的是, defer 属性对内嵌脚本无效。毕竟脚本内容就在 HTML 里了,完全不需要请求资源了好吧。
给 script 标签添加了 defer 属性后,脚本不会阻塞 DOM 树的构建,会先下载资源,然后等待到在 DOMContentLoaded 事件前执行。
DOMContentLoaded 事件的触发时机为初始 HTML 被构建完成时,此时 CSS、图片等资源不需要加载完,但我们的脚本要执行完。
如果多个 script 设置了 defer 属性,这几个 script 的执行顺序和声明顺序相同,即最前面的脚本先执行。并不是谁先下载谁先执行。
实际开发中,我们可以将业务代码脚本加上 defer 属性,放到更上层的 head 标签下。
这也是最新版 HtmlWebpackPlugin 插件的默认引入打包脚本的方式。
# 动态加载
<script>
const script = document.createElement('script');
script.src = 'app-a.js';
document.body.appendChild(script);
</script>
2
3
4
5
脚本里创建一个 script 元素,设置好 src,然后加载到 DOM 树上,接着脚本就会下载和执行了。
创建的 script 元素默认会给 async 设置为 true,即一旦下载好就立即执行。
如果你要加载有依赖关系的多个脚本,就需要将 async 设置为 false。
<script>
const script = document.createElement('script');
// 取消 async 加载方式
script.async = false;
script.src = 'app-a.js';
document.body.appendChild(script);
const script2 = document.createElement('script');
script2.async = false;
script2.src = 'app-b.js';
document.body.appendChild(script2);
</script>
<script>console.log('我还是所有脚本中最先执行的')</script>
2
3
4
5
6
7
8
9
10
11
12
13