在HTML标记中,我应该把<script>标签放在哪里?

1890
当嵌入JavaScript到HTML文档中时,放置<script>标签和包含的JavaScript的适当位置在哪里?我记得你不应该把它们放在<head>部分,但是把它们放在<body>部分的开头也不好,因为JavaScript将在页面完全渲染之前被解析(或类似的事情)。这似乎把<body>部分的结尾作为放置<script>标签的合理位置。
那么,放置<script>标签的正确位置在哪里?
(这个问题参考this question,其中建议将JavaScript函数调用从<a>标签移动到<script>标签。我特别使用jQuery,但更通用的答案也适用。)

1
如果您只是寻找一个简单的解决方案,并且正在使用像Jekyll这样的服务器端生成器,我建议将脚本与其一起包含。这样会更加简单! - cregox
3
如果您从搜索引擎进入此页面:许多答案并没有明确指出“脚本标签”应该放在哪里,而是在结尾处。如果“script”标签在“</body>”之后,HTML验证将导致“[Error: Stray start tag script]”(请检查选项“source”,然后单击“check”以查看HTML源代码)。它应该在“</body>”之前。(如果“script”标签位于最后,即“</html>”标记之后,则结果类似。) - Peter Mortensen
1
这也在 将<script>标签放在</body>标签之后是否有问题? 中得到了解决。 - Peter Mortensen
5
简而言之,将其放置在<head>标签内,并使用defer属性,或者更好的方法是将脚本设置为type='module'。现在是2022年。 - Beki
21个回答

2378

当浏览器加载带有<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 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 属性。这些属性告诉浏览器在下载脚本时可以继续解析文档。

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> 标签中,并使用 asyncdefer 属性。这样可以使您的脚本尽快下载而不会阻塞您的浏览器。

好的消息是,在不支持这些属性的2%浏览器上,您的网站应该仍然能够正确加载,同时加速另外98%的浏览器。

参考资料


8
我不清楚哪些操作会触及DOM,哪些不会。你能解释一下吗?像jquery.js这样的文件进行异步加载是安全的吗? - Doug
7
例如,document.write 操作的是 DOM。问题并不是脚本是否操作 DOM,而是它何时操作 DOM。只要所有 DOM 操作都在 domready 事件触发后发生,就没问题。jQuery是一个库,因此不会或不应该自行操作 DOM。 - Bart
28
这个答案含糊不清。现代浏览器在遇到可能影响HTML的同步脚本标签时并不会停止解析,它们只是停止渲染/执行,并继续乐观地解析以开始下载其他资源,如果没有影响HTML,那么这些资源很可能会随后被请求。 - Fabio Beltramini
47
为什么 asyncdefer 属性没有被使用?我的意思是,我查看了很多来自互联网的 HTML 源代码,但是我没有看到任何地方使用 asyncdefer 属性。。。? 为什么现在几乎没有使用 asyncdefer 属性呢?我查看了许多来自互联网的 HTML 源代码,但没有看到这些属性的使用。 - john c. j.
1
动态构建脚本标签并将其附加有什么优劣之分?例如:{ var se = document.createElement('script'); se.type = 'text/javascript'; se.src = "//code.com/myscript.js"; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(se, s); }这与使用defer类似吗?另外,如果我在其中添加“se.async = true;”会怎样?我注意到第三方提供商采用了这种方法。 - Sean
显示剩余13条评论

254

正如在 将脚本放在底部 中所述,在页面结束前的闭合body标记之前:

将脚本放在底部

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


7
同意该概念及其解释。但是如果用户开始操作页面会发生什么呢?假设我有一个 AJAX 下拉菜单,它将在页面对用户可见后开始加载,但在加载过程中,用户点击了它!如果一个“非常着急”的用户提交表单会怎样? - Hemant Tank
9
旧评论,但您可以通过默认禁用字段,然后在DOM完全加载时使用JS启用它们来解决问题。这似乎是Facebook现在正在做的事情。 - jmic
58
如果这是最佳实践,为什么Stack Overflow在<head>中包含所有脚本标签呢? :-P(说明:笑脸表情":-P"直译为“伸出舌头”,表示玩笑或者调皮的意思) - Philip
10
在一些情况下,特别是在使用大量ajax的网站中,将加载放在<head>标签中实际上可能会导致更快的加载时间。参见:http://encosia.com/dont-let-jquerys-document-ready-slow-you-down/(请注意,“live()”函数在jquery中已被弃用,但该文章仍适用于“on()”或“delegate”函数)。如@Hermant所指出的那样,将加载放在<head>标签中也可能需要保证正确的行为。最后,http://modernizr.com/docs/建议将其脚本放置在<head>中,理由在其网站上有解释。 - Nathan
4
警告:不,这是更好的做法。将其放在<head>标签中,并使用defer属性,甚至更好的方法是将您的脚本设置为type='module'。现在是2022年了。 - Beki
显示剩余4条评论

103

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

<script src="script.js" async></script>
<script src="script.js" defer></script>
<script src="script.js" async defer></script>
  • async 属性的脚本在可用时会异步执行。
  • defer 属性的脚本会在文档解析完成后执行。
  • async defer 属性的脚本如果不被支持,将会回退到 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的示例


2
这是今天的正确答案 - 使用这种方法意味着更容易维护你的小部件,无需进行复杂的<head>包含逻辑。 - Daniel Sokolowski
1
我很困惑为什么在包含jQuery时不能使用asyncdefer,就像您在第二个块中指定的那样:<script src="jquery.js" async></script>。您能解释一下为什么吗?根据被接受的答案,我认为我需要使用async标签来提高性能,这样即使jQuery仍在加载中,我的页面也可以加载。谢谢! - elbowlobstercowstand
3
99%的情况下,在网页中出现<script src=jquery.js>之后都会跟着一些 $(function(){ ... }) 代码块。异步加载不能保证浏览器在解析这些代码块时jQuery已经被加载,因此会出现 $ is not defined 的错误(如果从缓存加载jQuery,则可能不会出现错误)。我回答了一个关于异步加载jQuery并保留 $(function(){ ... }) 的问题。我会看看能否找到它,或者你可以查看这个问题:https://dev59.com/Z2Uq5IYBdhLWcg3wEcbu - Salman A
1
@SalmanA 谢谢!是的,我属于那99%。我首先需要加载jquery库,然后才能加载我的其余.js脚本。当我在jquery库脚本标签上声明asyncdefer时,我的.js脚本就无法工作了。我以为$(function(){ ... })可以保护它——看来不是这样。当前解决方案:我不在jquery库脚本上添加deferasync,但我会在我的后续.js脚本上添加async。注意:我做这一切的原因是为了让Google Page Speed高兴。再次感谢您的帮助!欢迎任何其他建议。 (或者链接到您之前的答案)。 :) - elbowlobstercowstand
@elbow 请参考https://dev59.com/Q3vZa4cB1Zd3GeqP-xeb#21013975,它只会给你一个想法,而不是完整的解决方案。相反,您可以搜索“jquery异步加载程序库”。 - Salman A
我完全同意你的观点! - Gadrawin

42
标准建议是将<script>标签放在文档的<body>元素末尾,以便不阻塞页面的呈现。但是有一些较新的方法可以提供更好的性能,如我在我的另一个回答中所描述的Google Analytics JavaScript文件的加载时间。 Steve Souders(客户端性能专家)有一些很棒的幻灯片,介绍了不同的并行加载外部JavaScript文件的技术、它们对加载时间和页面渲染的影响以及浏览器显示的“正在进行中”指示符的种类(例如状态栏中的“正在加载”,沙漏鼠标指针)。

39

6
请注意,如果您只是在本地文件系统上尝试一些东西而没有服务器,则此方法不起作用。至少在Chrome浏览器中,即使HTML和js文件来源于相同的文件系统,也会出现跨域错误,无法从HTML加载js文件。 - hippietrail

25
如果您正在使用jQuery,则可以将JavaScript代码放在任何您认为最好的位置,并使用$(document).ready()确保在执行任何函数之前已正确加载内容。
另外一点需要注意的是:我喜欢将所有的脚本标签放在<head>部分,因为这似乎是最干净的地方。

8
请注意,使用$(document).ready()并不意味着您可以在任何地方放置JavaScript代码 - 您仍然需要将其放置在包含jQuery的<script src=".../jquery.min.js">标签之后,以便$存在。 - Rory O'Kane
2
将脚本标签放在<head>部分不是最优的做法 - 这会延迟页面可见部分的显示,直到脚本加载完成。 - CyberMonk

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

在HTML文件中,脚本标签应该始终在 body 结束标签之前或者页面底部使用。

页面将会随着 HTML 和 CSS 加载而加载,稍后再加载 JavaScript。

如有需要请参考:

http://stevesouders.com/hpws/rule-js-bottom.php


3
实际上这回答了问题。我发现几乎所有已发布的示例都没有给出“页面末尾”正确的视觉上下文。 - Ken Ingram
2
这个答案非常误导人,很可能是错误的。GoogleMDN中的文章表明,同步JS(这里就是这种情况)总是会阻塞DOM构建和解析,这将导致延迟的首次渲染。因此,无论您在HTML文档中放置JS文件的位置如何,只要它是同步的,您都无法看到页面的内容,直到JS文件被获取并完成执行。 - Lingaraju E V
1
它还提及了2009年的观点,这些观点已经不再相关。 - toxaq
1
你的意思是“否则你将能够在加载js文件之前看到内容,这是不好的”吗? - ahnbizcad
1
最好的做法是不要将脚本标签放在body或html代码的末尾。就像其他声明性或元信息一样,这应该放在头部部分,减少无关内容的混乱。要习惯使用async或者更好的defer。不过Word Press可能有点棘手。它将JavaScript放在wp_head中,但没有使用defer。https://dev59.com/QmMk5IYBdhLWcg3w0RGU#20672324 - theking2
链接已经失效: "文件未找到。404 - 文件未找到。" - Peter Mortensen

13

在最佳位置放置<script>标签是在关闭</body>标记之前,这样下载并执行它不会阻止浏览器解析文档中的HTML。

此外,将JavaScript文件外部加载也有其优点,例如它将被浏览器缓存,并且可以加快页面加载时间,它将HTML和JavaScript代码分离,并有助于更好地管理代码库。

但现代浏览器还支持一些其他最佳方法,例如使用asyncdefer来加载外部JavaScript文件。

异步和延迟

通常,HTML页面逐行开始执行。当遇到外部JavaScript <script>元素时,HTML解析会停止,直到JavaScript下载并准备好执行。使用deferasync属性可以改变这种正常页面执行。

Defer

当使用defer属性时,JavaScript与HTML解析并行下载,但仅在完整的HTML解析完成后执行。

<script src="/local-js-path/myScript.js" defer></script>

异步

使用异步属性时,当遇到脚本时,JavaScript将立即下载,并在下载完成后与HTML解析一起异步(并行)执行。

<script src="/local-js-path/myScript.js" async></script>

何时使用哪些属性

  • 如果您的脚本独立于其他脚本且是模块化的,请使用async
  • 如果您同时使用async加载script1和script2,则两者将与HTML解析一起并行运行,只要它们下载并可用即可。
  • 如果您的脚本依赖于另一个脚本,则对于两者都使用defer
  • 当以这种顺序加载script1和script2时,使用defer,则保证script1首先执行,
  • 然后在script1完全执行后才会执行script2。
  • 如果script2依赖于script1,则必须这样做。
  • 如果您的脚本足够小,并且被类型为async的另一个脚本所依赖,则使用没有任何属性的脚本,并将其置于所有async脚本之上。

参考:外部JavaScript JS文件-优点、缺点、语法、属性


10

事实证明,它可能无处不在。

你可以使用类似于jQuery的方法来延迟执行,这样它就无所谓放置位置了(解析期间会有一定的性能损失)。


1
XHTML在正文中包含脚本标签时,无论是严格的还是过渡的,都可以验证。但是样式标签只能在头部中使用。 - I.devries
关于“它放在哪里都无所谓”的问题:但是结果是否不符合格式良好的HTML呢? - Peter Mortensen
这个答案特别没有用,因为它根本没有回答我应该把<script>标签放在哪里的问题(而且还是错误的)。 - TylerH
好的,没问题。我已经不再那么幼稚了,已经过去13年了。 - Allain Lalonde

6
最保守(也是广泛接受的)答案是“在结束标签之前的底部”,因为这样整个DOM都会在任何东西开始执行之前加载完毕。
有一些持不同意见者,出于各种原因,从有意地以页面onload事件开始执行的可行做法开始。

关于“在底部”的问题:是指在结束的</body>标签之前吗?你能否更清楚地表述一下?(但请不要包含“编辑:”、“更新:”或类似的内容——回答应该看起来像是今天写的)。 - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接