当浏览器加载带有<script>
标签的网站时,会发生以下情况:
- 获取HTML页面(例如index.html)
- 开始解析HTML
- 解析器遇到引用外部脚本文件的
<script>
标签。
- 浏览器请求该脚本文件。同时,解析器会阻塞并停止解析页面上的其他HTML。
- 一段时间后,脚本被下载并随后执行。
- 解析器继续解析剩余的HTML文档。
第4步会导致糟糕的用户体验。您的网站基本上会停止加载,直到所有脚本都已下载。如果有一件事让用户讨厌,那就是等待网站加载。
为什么会出现这种情况?
任何脚本都可以通过 document.write()
或其他DOM操作插入自己的HTML。这意味着解析器必须等待脚本下载并执行后才能安全地解析文档的其余部分。毕竟,脚本可能在文档中插入了自己的HTML。
然而,大多数JavaScript开发人员不再在文档加载时操作DOM。相反,他们会等到文档加载完成后再进行修改。例如:
<html>
<head>
<title>My Page</title>
<script src="my-script.js"></script>
</head>
<body>
<div id="user-greeting">Welcome back, user</div>
</body>
</html>
JavaScript:
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("user-greeting").textContent = "Welcome back, Bart";
});
由于您的浏览器不知道 my-script.js 只有在下载并执行之后才会修改文档,所以解析器停止解析。
过时的建议
解决这个问题的旧方法是将 <script>
标签放在您的 <body>
底部,因为这可以确保解析器一直解析直到最后。
这种方法也有它自己的问题:浏览器无法在整个文档被解析之前开始下载脚本。对于具有大型脚本和样式表的大型网站来说,尽快下载脚本非常重要以提高性能。如果您的网站在 2 秒内没有加载完成,人们就会转向另一个网站。
在最佳解决方案中,浏览器将在尽可能短的时间内开始下载您的脚本,同时解析文档的其余部分。
现代方法
现代浏览器支持在脚本上使用 async
和 defer
属性。这些属性告诉浏览器在下载脚本时可以继续解析文档。
async
<script src="path/to/script1.js" async></script>
<script src="path/to/script2.js" async></script>
带有async属性的脚本是异步执行的,这意味着该脚本在下载后立即执行,而不会在此期间阻塞浏览器。
这意味着可能会在脚本1之前下载和执行脚本2。
根据http://caniuse.com/#feat=script-async,所有浏览器中有97.78%支持此功能。
defer
<script src="path/to/script1.js" defer></script>
<script src="path/to/script2.js" defer></script>
使用 defer 属性的脚本按顺序执行(即先执行脚本1,然后执行脚本2)。这也不会阻塞浏览器。
与 async 脚本不同,defer 脚本仅在整个文档加载完成后才会执行。
(要了解更多信息并查看一些非常有用的关于 async、defer 和普通脚本之间差异的可视化表示,请参见本答案的参考资料部分的前两个链接)
结论
当前的最佳实践是将脚本放置在 <head>
标签中,并使用 async
或 defer
属性。这样可以使您的脚本尽快下载而不会阻塞您的浏览器。
好的消息是,在不支持这些属性的2%浏览器上,您的网站应该仍然能够正确加载,同时加速另外98%的浏览器。
参考资料
<head>
标签内,并使用defer
属性,或者更好的方法是将脚本设置为type='module'
。现在是2022年。 - Beki