什么时候应该使用内联 JavaScript 和外部 JavaScript?

133
我想知道在性能和易维护性方面,何时应该包含外部脚本或将其与HTML代码内联编写。
一般的做法是什么?
现实世界的场景是,我有几个需要客户端表单验证的HTML页面。为此,我使用一个jQuery插件,将其包含在所有这些页面中。但问题是,我应该:
- 在代码中内联编写配置此脚本的代码片段? - 将所有代码片段包含在一个文件中,该文件在所有这些HTML页面中共享? - 将每个代码片段包含在一个单独的外部文件中,每个HTML页面一个文件?
19个回答

118

在这个答案最初发布的时候(2008年),规则很简单:所有脚本都应该是外部的,这样便于维护和提高性能。

(为什么提高性能?因为如果代码是分离的,它可以更容易地被浏览器缓存。)

JavaScript 不属于 HTML 代码,如果它包含特殊字符(例如 <, >),甚至会引起问题。

现今,Web 可扩展性已经发生了变化。由于进行多个 HTTP 请求所需的延迟,减少请求次数已经成为一个有效的考虑因素。这使得答案变得更加复杂:在大多数情况下,将 JavaScript 作为外部脚本仍然是推荐的做法。但是对于某些场景,尤其是非常小的代码片段,将它们内联到网站的 HTML 中是有意义的。


6
@Nick: 大多数问题都可以克服。然而最好的做法是不要在一开始就产生这些问题。 - Konrad Rudolph
18
有时候将函数内联可以提高性能。看看google.com的源代码,他们知道自己在做什么。 - callum
15
谷歌的用例不同于99.999999%的网站。当然,他们进行极其仔细的测量,即使最小的差异也很重要。但是,仅仅因为他们发现在他们特定的用例中,内联脚本效果更好(可能是因为脚本经常变化?),并不意味着我们可以从中推导出一个普遍规则,甚至不应该忽略“传统”的规则(将脚本外部化)。 - Konrad Rudolph
8
@KonradRudolph - 我同意,不应该从Google的方法中推导出任何一般性规则。我只是想说这是一个提示,值得质疑你在答案中提到的“规则”。无论如何,我认为Google这样做的原因是为了减少HTTP请求,这可能会使超过0.000001%的网站受益。带宽正在变得更高,但往返时间却没有变化。有时删除整个串行HTTP请求比外部JS的缓存好处更好。当然,这取决于您的JS大小。 - callum
5
尽管这是正确的,但缓存仍然是一个关键点并且非常重要。减少往返次数只有在您的访问者不返回(那么您将无法获得足够的页面点击量来使其成为问题),或者如果您的内容变化频繁以至于缓存脚本文件没有任何好处时才重要。 - Konrad Rudolph
显示剩余22条评论

31

可维护性确实是将它们保留为外部文件的原因,但如果配置只有一行(或者通常比获得这些文件所需的HTTP开销更短),则在性能方面最好将其内联。始终要记住,每个HTTP请求都会产生一定的执行时间和流量开销。

自然而然地,当你的代码长度超过几行,并且不仅仅针对一个页面时,所有这些都变得无关紧要。当你想要重用该代码时,将其制作为外部文件。如果不这样做,请查看其大小,然后再考虑是否需要将其制作为外部文件。


5
这是我关注的其中一个问题。为几行代码而单独发出HTTP请求似乎浪费资源。 - Dan
你可以发布一份代码配置的样例吗?我认为如果字符数不超过300个,而且是页面特定的内容,可以将其内联。 - Horst Gutmann
@Dan请记住,单独请求仅在第一次发生。如果您希望用户加载页面的次数超过一次,则缓存外部资源(即使只是几行代码)显然比在n = 2+页加载时通过网络等待这些几行代码的字节更快。 - jinglesthula
@HorstGutmann,将文件外部化如何有助于可维护性?我个人尽可能喜欢使用外部JS,但是否有客观因素使其更易于维护? - jinglesthula
@jinglesthula 一个例子是,如果你想在多个页面上使用相同的代码(或类似的代码),那么将相同的代码放置在多个位置会阻碍可维护性。当然,有一些方法可以解决这个问题,但让我们假设这里采用最朴素的实现方式。而且,当代码存储在专门的文件中时,静态分析代码也更容易(例如eslint等)。 - Horst Gutmann

22

如果你只关心性能,那么这个帖子中的大部分建议都是完全错误的,在 SPA 时代变得越来越错误,因为我们可以认为没有 JS 代码页面是无用的。我花费了无数小时优化 SPA 页面加载时间,并使用不同的浏览器验证了这些结果。通过重新编排您的 HTML,性能提升可能会非常显著。

要获得最佳性能,您必须将页面视为两级火箭。这两个阶段大致对应于 <head><body> 阶段,但您应该将它们视为 <static><dynamic>。静态部分基本上是一个字符串常量,您应该尽可能快地将其推到响应管道中。如果您使用了许多设置 cookie 的中间件(这些需要在发送 HTTP 内容之前设置),这可能有点棘手,但原则上只需刷新响应缓冲区,希望在服务器上跳转到某些模板代码(razor、php 等)之前。这可能听起来很困难,但我解释错了,因为它实际上相当简单。正如您可能已经猜到的那样,这个静态部分应该包含所有 JavaScript 内联和压缩。它可能看起来像这样:

<!DOCTYPE html>
     <html>
         <head>
             <script>/*...inlined jquery, angular, your code*/</script>
             <style>/* ditto css */</style>
         </head>
         <body>
             <!-- inline all your templates, if applicable -->
             <script type='template-mime' id='1'></script>
             <script type='template-mime' id='2'></script>
             <script type='template-mime' id='3'></script>

由于发送这部分内容几乎不花费任何成本,所以您可以预期客户端将在连接到服务器后大约5ms +延迟左右开始接收此部分内容。假定服务器距离合理,这种延迟可能在20毫秒到60毫秒之间。浏览器将在获得此部分内容后立即开始处理,处理时间通常会比传输时间支配因子要多20倍或更多,这现在是您用于<dynamic>部分的服务端处理的平均分摊时间。

浏览器(chrome,其他浏览器可能慢20%)处理inline jquery + signalr + angular + ng animate + ng touch + ng routes + lodash大约需要50ms。这本身非常惊人。大多数Web应用程序的代码量都比这些流行库加在一起少,但是假设您拥有同样多的代码,那么我们将在客户端赢得延迟+100ms的处理(这种延迟胜利来自第二个转移块)。当第二个块到达时,我们已经处理了所有js代码和模板,可以开始执行dom变换。

您可能会认为这种方法与内联概念无关,但事实并非如此。如果您不是内联,而是链接到CDN或您自己的服务器,则浏览器必须打开另一个连接并延迟执行。由于这种执行基本上是免费的(因为服务器端正在与数据库通信),因此必须清楚地了解所有这些跳跃的成本都比根本不进行任何跳跃更高。如果存在浏览器怪癖,说外部js执行得更快,我们可以测量哪个因素占主导地位。我的测量结果表明,在此阶段,额外的请求会影响性能。

我经常在优化SPA应用程序方面工作。人们通常认为数据量很重要,但实际上是延迟和执行经常占主导地位。我列出的压缩库总共添加了300kb的数据,而仅68 kb经过gzip压缩,或者在2mbit 3g / 4g手机上下载需要200ms,这恰好是在同一手机上检查它是否已经在其缓存中具有相同数据的延迟,即使它被代理缓存,因为移动延迟税(手机到塔延迟)仍然适用。与此同时,首跳延迟较低的桌面连接通常拥有更高的带宽。

简而言之,现在(2014年),最好内联所有脚本,样式和模板。

编辑(2016年5月)

随着JS应用程序的不断增长,我的一些有效负载现在堆叠到3+兆字节的压缩代码,显然,至少常见库不应再内联。


我没有理解“现在是您用于服务器端处理<dynamic>部分的摊销窗口”的部分 - 服务器会处理它所需的任何内容,然后才提供整个呈现的HTML(头+正文),那之后还需要进行哪些其他服务器处理? - BornToCode
@BornToCode 的想法是在服务器端有任务需要处理的同时,给客户端一些事情去做。因为客户端库需要被解释 - 最好在服务器上进行任何计算之前启动该过程。分摊窗口是客户端处理 JS 所需的时间。如果你编排一个两阶段火箭,你可以免费获得这个窗口。 - Gleno

21

将JavaScript外部化是雅虎性能规则之一: http://developer.yahoo.com/performance/rules.html#external

虽然硬性规定总是将脚本外部化通常是一个好选择,但在某些情况下,您可能希望内联一些脚本和样式表。但是,您应该只内联那些您知道会提高性能的内容(因为您已经进行了测量)。


1
我认为雅虎也建议将所有的Javascript代码放在一个HTTP调用中 - 这并不意味着在开发过程中这些脚本都应该在同一个文件中。 - Paul Shannon
1
另外,正如上面所提到的,HTTP/2也改变了“一次调用”的做法。 - jinglesthula

14

9

实际上,使用内联JavaScript是有充分理由的。如果js足够小(只有一行),我倾向于使用内联JavaScript,因为有两个因素:

  • 局部性。没有必要浏览外部文件来验证某些JavaScript的行为。
  • AJAX。如果您通过AJAX刷新页面的某个部分,则可能会失去该部分的所有DOM处理程序(onclick等),这取决于您如何绑定它们。例如,使用jQuery,您可以使用livedelegate方法来规避此问题,但我发现如果js足够小,最好将其放在内联中。

6

你应该始终使用外部脚本的另一个原因是更容易过渡到内容安全策略(CSP)。CSP默认禁止所有内联脚本,使您的网站更加抵御跨站脚本攻击(XSS)。


4

我能为内联JavaScript提供的唯一防御是,使用带有.NET MVC的强类型视图时,您可以在JavaScript中引用C#变量,这对我非常有用。


4
我会查看所需的代码并将其分成尽可能多的单独文件。每个JS文件仅包含一个“逻辑集”中的函数等,例如,所有与登录相关的函数都在一个文件中。
然后,在每个HTML页面的站点开发过程中,您只需要包括需要的文件。 当您启动网站时,您可以通过将每个页面需要的所有JS文件合并为一个文件来进行优化。

3

三个考虑因素:

  • 你需要多少代码(有时库是一级消费者)?
  • 特定性:这段代码是否仅在该特定文档或元素的上下文中起作用?
  • 文档中的每段代码都会使其变得更长,从而变慢。此外,SEO考虑因素表明,您应尽量减少内部脚本...

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