我应该在 HTML 标记的哪里放置 <script> 标记?

在将 JavaScript 嵌入 HTML 文档中时,将<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 属性的脚本是异步执行的。这意味着脚本在下载后立即执行,同时不会阻塞浏览器。
这意味着可以在脚本 1 之前下载并执行脚本 2。

根据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的示例。

由 Yahoo! 提倡的标准建议。出色的性能团队将<script>标记放在文档正文的末尾,以使它们不会阻止页面的呈现。

但是,如以下有关 Google Analytics(分析)JavaScript 文件的加载时间的答案所述,有些新方法可以提供更好的性能:

Steve Souders(客户端性能专家)提供了一些很棒的幻灯片 ,这些幻灯片涉及:

  • 并行加载外部 JavaScript 文件的不同技术
  • 它们对加载时间和页面呈现的影响
  • 浏览器显示什么样的 “进行中” 指示(例如状态栏中的 “正在加载”,沙漏鼠标光标)。

如果您使用的是 JQuery,则将 javascript 放在最合适的位置,并使用$(document).ready()确保在执行任何功能之前正确加载了所有内容。

附带说明:我喜欢<head>部分中的所有脚本标签,因为这似乎是最干净的地方。

2019 年的现代方法是使用 ES6 模块类型脚本

<script type="module" src="..."></script>

默认情况下,模块是异步加载和延迟的。也就是说,您可以将它们放置在任何地方,它们将并行加载,并在页面加载完成后执行。

此处描述了脚本和模块之间的区别:

https://stackoverflow.com/a/53821485/731548

与脚本相比,模块的执行描述如下:

https://developers.google.com/web/fundamentals/primers/modules#defer

支持如下所示:

https://caniuse.com/#feat=es6-module

XHTML 不会验证脚本是否位于 head 元素之外的任何其他位置。 事实证明它可以无处不在。

您可以使用 jQuery 之类的方法推迟执行,因此放置位置无关紧要(解析期间对性能的影响很小)。

<script src="myjs.js"></script>
</body>

脚本标记应始终在主体关闭之前或HTML文件的底部之前使用。

那么您可以在加载js文件之前先查看页面的内容。

如果需要,请检查此内容: http : //stevesouders.com/hpws/rule-js-bottom.php

常规的(并且被广泛接受的)答案是 “在最底层”,因为这样,在任何内容开始执行之前,整个 DOM 都将被加载。

出于各种原因,有一些持不同政见者从可用的实践开始,故意以页面 onload 事件开始执行。

取决于脚本及其用法,最好的方法(就页面加载和渲染时间而言)可能是不使用常规的

有一些不同的技术,但是最直接的方法是在触发 window.onload 事件时使用 document.createElement(“script”)。然后,在页面本身已呈现时,将首先加载脚本,因此不会影响用户必须等待页面出现的时间。

自然,这要求脚本本身不需要呈现页面。

有关更多信息,请参阅 Steve Souders(YSlow 的创建者,但现在是 Google)的帖子Coupling async scripts