当浏览器加载带有<script>
标签的网站时,会发生以下情况:
- 提取HTML页面(例如index.html)
- 开始解析HTML
- 解析器遇到一个
<script>
引用外部脚本文件的标记。
- 浏览器请求脚本文件。同时,解析器将阻止并停止解析页面上的其他HTML。
- 一段时间后,脚本将被下载并随后执行。
- 解析器将继续解析HTML文档的其余部分。
步骤4会导致不良的用户体验。您的网站基本上会停止加载,直到您下载了所有脚本。如果用户讨厌一件事,它正在等待网站加载。
为什么会发生这种情况?
任何脚本都可以通过document.write()
或其他DOM操作插入自己的HTML 。这意味着解析器必须等到脚本被下载并执行后,才能安全地解析文档的其余部分。毕竟,脚本可能已经在文档中插入了自己的HTML。
但是,大多数JavaScript开发人员在文档加载时不再处理DOM 。取而代之的是,他们等到文档加载完成后再进行修改。例如:
<!-- index.html -->
<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:
// my-script.js
document.addEventListener("DOMContentLoaded", function() {
// this function runs when the DOM is ready, i.e. when the document has been parsed
document.getElementById("user-greeting").textContent = "Welcome back, Bart";
});
由于您的浏览器不知道my-script.js在下载并执行文档之前不会修改文档,因此解析器将停止解析。
过时的推荐
解决此问题的旧方法是将<script>
标签放在的底部<body>
,因为这样可以确保解析器直到最后都不会被阻塞。
这种方法有其自身的问题:浏览器在解析整个文档之前无法开始下载脚本。对于具有大型脚本和样式表的大型网站,能够尽快下载脚本对于提高性能非常重要。如果您的网站在2秒钟内未加载,人们将转到另一个网站。
在最佳解决方案中,浏览器将尽快开始下载脚本,同时解析文档的其余部分。
现代方法
如今,浏览器支持脚本的async
和defer
属性。这些属性告诉浏览器在下载脚本时继续解析是安全的。
异步的
<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%支持此功能。
推迟
<script src="path/to/script1.js" defer></script>
<script src="path/to/script2.js" defer></script>
具有defer属性的脚本按顺序执行(即,第一个脚本1,然后是脚本2)。这也不会阻止浏览器。
与异步脚本不同,延迟脚本仅在整个文档加载后才执行。
根据http://caniuse.com/#feat=script-defer的说法,所有浏览器中的97.79%支持此功能。98.06%至少部分支持它。
关于浏览器兼容性的重要说明:在某些情况下IE <= 9可能会无序执行延迟的脚本。如果您需要支持这些浏览器,请先阅读此内容!
结论
当前最新的技术是将脚本放入<head>
标签中并使用async
或defer
属性。这样一来,您的脚本就可以尽快下载,而不会阻止浏览器。
好消息是,您的网站仍应在不支持这些属性的2%的浏览器上正确加载,同时加快其他98%的浏览器的速度。