文章内容
2021/12/17 17:06:05,作 者: 黄兵
script 标签 async defer 总结
最近再优化网站,结果提示:下面的关键请求链显示了以高优先级加载的资源。请考虑缩短链长、缩减资源的下载文件大小,或者推迟下载不必要的资源,从而提高网页加载速度。
对于 script 资源,我们使用 async
defer
的方式加载资源,在 script 标签中 async
和 defer
具体作用是什么呢?
在现代网站中,脚本通常比 HTML“更重”:它们的下载量更大,处理时间也更长。
当浏览器加载 HTML 并遇到 <script>...</script>
标记时,它无法继续构建 DOM。它必须立即执行脚本。外部脚本 <script src="..."></script>
也会发生同样的情况:浏览器必须等待脚本下载,执行下载的脚本,然后才能处理页面的其余部分。
这导致了两个重要的问题:
- 脚本看不到它们下面的 DOM 元素,因此它们无法添加处理程序等。
- 如果页面顶部有一个庞大的脚本,它会“阻塞页面”。在下载并运行之前,用户无法看到页面内容:
<p>...content before script...</p>
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- This isn't visible until the script loads -->
<p>...content after script...</p>
有一些解决方法。例如,我们可以在页面底部放置一个脚本。然后它可以看到其上方的元素,并且不会阻止页面内容的显示:
<body>
...all content is above the script...
<script src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
</body>
但是这个解决方案远非完美。例如,浏览器只有在下载完整的 HTML 文档后才会注意到脚本(并可以开始下载它)。对于长 HTML 文档,这可能是一个明显的延迟。
这些东西对于使用非常快的连接的人来说是看不见的,但世界上许多人的互联网速度仍然很慢,并且使用的移动互联网连接远非完美。
幸运的是,有两个<script>
属性可以为我们解决问题:defer
和 async
.
defer
该 defer
属性告诉浏览器不要等待脚本。相反,浏览器将继续处理 HTML,构建 DOM。该脚本在“后台”加载,然后在完全构建 DOM 时运行。
这是与上面相同的示例,但具有 defer
:
<p>...content before script...</p>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<!-- visible immediately -->
<p>...content after script...</p>
换句话说:
defer
永远不会阻止页面的脚本。- 脚本
defer
总是在 DOM 准备好时(但在DOMContentLoaded
事件之前)执行。
下面的例子演示了第二部分:
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready after defer!"));
</script>
<script defer src="https://javascript.info/article/script-async-defer/long.js?speed=1"></script>
<p>...content after scripts...</p>
- 页面内容立即显示。
DOMContentLoaded
事件处理程序等待延迟的脚本。它仅在下载并执行脚本时触发。
延迟脚本保持它们的相对顺序,就像常规脚本一样。
比方说,我们有两个延迟脚本: long.js
和 small.js
:
<script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
<script defer src="https://javascript.info/article/script-async-defer/small.js"></script>
浏览器扫描页面中的脚本并并行下载它们,以提高性能。所以在上面的例子中,两个脚本并行下载。在 small.js
可能第一个完成。
...但该 defer
属性除了告诉浏览器“不要阻止”之外,还确保保持相对顺序。因此,即使 small.js
先加载,它仍会在 long.js
执行后等待并运行。
当我们需要加载一个 JavaScript 库,然后加载一个依赖它的脚本时,这可能很重要。
🛑该 defer
属性仅适用于外部脚本
async
该 async
属性是有点像 defer
。它还使脚本非阻塞。但它在行为上有重要区别。
该 async
属性表示一个脚本是完全独立的:
- 浏览器不会阻止
async
脚本(如defer
)。 - 其他脚本不等待
async
脚本,async
脚本也不等待它们。 DOMContentLoaded
和异步脚本不会互相等待:DOMContentLoaded
可能发生在异步脚本之前(如果异步脚本在页面完成后完成加载)- ...或在异步脚本之后(如果异步脚本很短或在 HTTP 缓存中)
换句话说,async
脚本在后台加载并在准备好时运行。DOM 和其他脚本不等待它们,它们也不等待任何东西。加载时运行的完全独立的脚本。尽可能简单,对吧?
这是一个类似于我们所看到的示例 defer
:两个脚本 long.js
和 small.js
,但现在使用 async
而不是 defer
。
他们不等待对方。无论首先加载(可能 small.js
) - 首先运行:
<p>...content before scripts...</p>
<script>
document.addEventListener('DOMContentLoaded', () => alert("DOM ready!"));
</script>
<script async src="https://javascript.info/article/script-async-defer/long.js"></script>
<script async src="https://javascript.info/article/script-async-defer/small.js"></script>
<p>...content after scripts...</p>
- 页面内容立即显示:
async
不会阻止它。 DOMContentLoaded
之前和之后都可能发生async
,这里不做任何保证。- 较小的脚本
small.js
排在第二位,但可能在之前加载long.js
,因此small.js
首先运行。虽然,它可能是long.js
首先加载,如果缓存,那么它首先运行。换句话说,异步脚本以“加载优先”的顺序运行。
当我们将独立的第三方脚本集成到页面中时,异步脚本非常有用:计数器、广告等,因为它们不依赖于我们的脚本,我们的脚本不应该等待它们:
<!-- Google Analytics is usually added like this -->
<script async src="https://google-analytics.com/analytics.js"></script>
🛑该async
属性仅适用于外部脚本
动态脚本
还有一种更重要的向页面添加脚本的方法。
我们可以创建一个脚本并使用 JavaScript 动态地将其附加到文档中:
let script = document.createElement('script');
script.src = "/article/script-async-defer/long.js";
document.body.append(script); // (*)
脚本在附加到文档后立即开始加载(*)
。
默认情况下,动态脚本表现为“async”。
原因是:
- 他们什么都不等,什么也等不着。
- 首先加载的脚本 - 首先运行(“加载优先”顺序)。
如果我们明确设置 script.async=false
. 然后脚本将按照文档顺序执行,就像 defer
.
在本例中,loadScript(src)
函数添加了一个脚本并设置 async
为 false
。
所以 long.js
总是先运行(因为它是最先添加的):
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
script.async = false;
document.body.append(script);
}
// long.js runs first because of async=false
loadScript("/article/script-async-defer/long.js");
loadScript("/article/script-async-defer/small.js");
如果没有 script.async=false
,脚本将按默认加载优先顺序( small.js
可能是第一个)执行。
同样,与 defer
一样,如果我们想要加载一个库然后另一个依赖它的脚本,顺序很重要。
总结
对于 async
和 defer
有一个共同的东西:这些脚本的下载不会阻止网页渲染。因此用户可以阅读页面内容并立即熟悉页面。
但它们之间也有本质的区别:
命令 | DOMContentLoaded | |
async | 加载优先顺序。它们的文档顺序无关紧要——先加载哪个先运行 | 无关。可能在文档尚未完全下载时加载和执行。如果脚本很小或被缓存,并且文档足够长,就会发生这种情况。 |
defer | 文档顺序(按照文档中的顺序)。 | 在加载和解析文档之后执行(如果需要,他们会等待),就在DOMContentLoaded . |
在实践中,defer
用于需要整个 DOM 和/或它们的相对执行顺序的脚本很重要。
并且 async
用于独立脚本,例如计数器或广告。它们的相对执行顺序无关紧要。
文章来源于:Scripts: async, defer
黄兵个人博客收集与整理。
评论列表