文章内容

2021/12/17 17:06:05,作 者: 黄兵

script 标签 async defer 总结


最近再优化网站,结果提示:下面的关键请求链显示了以高优先级加载的资源。请考虑缩短链长、缩减资源的下载文件大小,或者推迟下载不必要的资源,从而提高网页加载速度。

对于 script 资源,我们使用 async defer 的方式加载资源,在 script 标签中 asyncdefer 具体作用是什么呢?

在现代网站中,脚本通常比 HTML“更重”:它们的下载量更大,处理时间也更长。

当浏览器加载 HTML 并遇到 <script>...</script> 标记时,它无法继续构建 DOM。它必须立即执行脚本。外部脚本 <script src="..."></script> 也会发生同样的情况:浏览器必须等待脚本下载,执行下载的脚本,然后才能处理页面的其余部分。

这导致了两个重要的问题:

  1. 脚本看不到它们下面的 DOM 元素,因此它们无法添加处理程序等。
  2. 如果页面顶部有一个庞大的脚本,它会“阻塞页面”。在下载并运行之前,用户无法看到页面内容:
<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>属性可以为我们解决问题:deferasync.


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>

换句话说:

  1. defer 永远不会阻止页面的脚本。
  2. 脚本 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>
  1. 页面内容立即显示。
  2. 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.jssmall.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) 函数添加了一个脚本并设置 asyncfalse

所以 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 一样,如果我们想要加载一个库然后另一个依赖它的脚本,顺序很重要。


总结

对于 asyncdefer 有一个共同的东西:这些脚本的下载不会阻止网页渲染。因此用户可以阅读页面内容并立即熟悉页面。

但它们之间也有本质的区别:


命令
DOMContentLoaded
async
加载优先顺序它们的文档顺序无关紧要——先加载哪个先运行
无关。可能在文档尚未完全下载时加载和执行。如果脚本很小或被缓存,并且文档足够长,就会发生这种情况。
defer
文档顺序(按照文档中的顺序)。
在加载和解析文档之后执行(如果需要,他们会等待),就在DOMContentLoaded.

在实践中,defer 用于需要整个 DOM 和/或它们的相对执行顺序的脚本很重要。

并且 async 用于独立脚本,例如计数器或广告。它们的相对执行顺序无关紧要。


文章来源于:Scripts: async, defer

黄兵个人博客收集与整理。

分享到:

发表评论

评论列表