协慌网

登录 贡献 社区

我应该在哪里将 <script> 标记放在 HTML 标记中?

在 JavaScript 文档中嵌入 JavaScript 时,放置<script>标记和包含 JavaScript 的适当位置在哪里?我似乎记得你不应该将它们放在<head>部分,但放在<body>部分的开头也是不好的,因为在完全呈现页面之前必须解析 JavaScript(或类似的东西)。这似乎将<body>部分的末尾作为<script>标记的逻辑位置。

所以,哪里把正确的地方<script>标记?

(这个问题引用了这个问题 ,其中建议 JavaScript 函数调用应该从<a>标签移动到<script>标签。我专门使用 jQuery,但更一般的答案也是合适的。)

答案

以下是浏览器加载带有<script>标记的网站时发生的情况:

  1. 获取 HTML 页面(例如 index.html)
  2. 开始解析 HTML
  3. 解析器遇到引用外部脚本文件的<script>标记。
  4. 浏览器请求脚本文件。同时,解析器阻止并停止解析页面上的其他 HTML。
  5. 一段时间后,脚本被下载并随后执行。
  6. 解析器继续解析 HTML 文档的其余部分。

步骤#4 会导致糟糕的用户体验。在您下载所有脚本之前,您的网站基本上会停止加载。如果有一件事是用户讨厌它正在等待网站加载。

为什么会发生这种情况?

任何脚本都可以通过document.write()或其他 DOM 操作插入自己的 HTML。这意味着解析器必须等到脚本下载并执行才能安全地解析文档的其余部分。毕竟,脚本可以在文档中插入自己的 HTML。

然而,大多数 JavaScript 开发人员不再操作 DOM 文档加载 。相反,它们会等到文档加载后再修改它。例如:

<!-- index.html -->
<html>
    <head>
        <title>My Page</title>
        <script type="text/javascript" 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 秒内没有加载,人们将转到另一个网站。

在最佳解决方案中,浏览器将尽快开始下载脚本,同时解析文档的其余部分。

现代的方法

今天,浏览器支持脚本上的asyncdefer属性。这些属性告诉浏览器在下载脚本时继续解析是安全的。

异步

<script type="text/javascript" src="path/to/script1.js" async></script>
<script type="text/javascript" src="path/to/script2.js" async></script>

具有 async 属性的脚本是异步执行的。这意味着脚本在下载后立即执行,同时不会阻止浏览器。
这意味着脚本 2 可以在脚本 1 之前下载并执行。

根据http://caniuse.com/#feat=script-async,94.57%的浏览器支持此功能。

延缓

<script type="text/javascript" src="path/to/script1.js" defer></script>
<script type="text/javascript" src="path/to/script2.js" defer></script>

具有 defer 属性的脚本按顺序执行(即第一个脚本 1,然后是脚本 2)。这也不会阻止浏览器。

与异步脚本不同,延迟脚本仅在加载整个文档后执行。

根据http://caniuse.com/#feat=script-defer,94.59%的浏览器支持此功能。 94.92%至少部分支持它。

关于浏览器兼容性的重要说明:在某些情况下,IE <= 9 可能无序执行延迟脚本。如果您需要支持这些浏览器,请先阅读此内容

结论

当前最先进的技术是将脚本放在<head>标记中并使用asyncdefer属性。这样可以在不阻止浏览器的情况下尽快下载脚本。

好消息是,您的网站仍应在 6%不支持这些属性的浏览器上正确加载,同时加快其他 94%。

就在关闭身体标签之前,如上所述

http://developer.yahoo.com/performance/rules.html#js_bottom

把脚本放在底部

脚本引起的问题是它们阻止了并行下载。 HTTP / 1.1 规范建议浏览器每个主机名并行下载不超过两个组件。如果您从多个主机名提供图像,则可以并行执行两次以上的下载。但是,在下载脚本时,即使在不同的主机名上,浏览器也不会启动任何其他下载。

非阻塞脚本标记可以放在任何地方:

<script src="script.js" async></script>
<script src="script.js" defer></script>
<script src="script.js" async defer></script>
  • async脚本一旦可用就会异步执行
  • 文档完成解析后执行defer脚本
  • 如果不支持async defer脚本将回退到延迟行为

这样的脚本将在文档就绪后异步执行,这意味着你不能这样做:

<script src="jquery.js" async></script>
<script>jQuery(something);</script>
<!--
  * might throw "jQuery is not defined" error
  * defer will not work either
-->

或这个:

<script src="document.write(something).js" async></script>
<!--
  * might issue "cannot write into document from an asynchronous script" warning
  * defer will not work either
-->

或这个:

<script src="jquery.js" async></script>
<script src="jQuery(something).js" async></script>
<!--
  * might throw "jQuery is not defined" error (no guarantee which script runs first)
  * defer will work in sane browsers
-->

或这个:

<script src="document.getElementById(header).js" async></script>
<div id="header"></div>
<!--
  * might not locate #header (script could fire before parser looks at the next line)
  * defer will work in sane browsers
-->

话虽如此,异步脚本提供了以下优势:

  • 并行下载资源
    浏览器可以并行下载样式表,图像和其他脚本,而无需等待脚本下载和执行。
  • 来源订单独立性
    您可以将脚本放在头部或主体内而不必担心阻塞(如果您使用的是 CMS,则非常有用)。执行顺序仍然很重要。

可以通过使用支持回调的外部脚本来规避执行顺序问题。许多第三方 JavaScript API 现在支持非阻塞执行。以下是异步加载 Google Maps API的示例。