我应该将我的JavaScript拆分成多个文件吗?

46

我习惯使用Java编程,我们知道每个对象通常定义在自己的文件中。我喜欢这样做,因为它让代码更易于管理和操作。

现在我开始使用JavaScript编程,并发现自己希望在单个页面上使用不同的脚本时采用单独的文件进行编写。目前,我只使用了几个 .js 文件,因为我担心如果我使用更多,未来可能会遇到一些难以预料的问题,例如循环引用。

简而言之,将我的脚本分成多个文件是一个不好的实践吗?


20
为了缩短下载时间,您应始终尝试将脚本制作成单个大文件。但是,如果您使用了压缩工具(您应该这样做),它们可以为您合并多个源文件。因此,您可以继续在多个文件上工作,然后将它们压缩为一个单独的文件以进行分发。 - Dave
2
@Dave,你一定要把那个发表为答案! - alestanis
无论你使用什么编程语言,每个文件只放一个类(模块)是一个好主意,如果你关心面向对象编程。你所要做的就是将所有文件压缩成一个文件。 - Ruan Mendes
不一定 - 您的浏览器在同时下载文件时受到限制,只有一个连接可能更有效。但是您也不需要在五十个不同的文件中使用三行JS。我建议您研究应用程序模块设计和Require.js。 - Jared Farrish
6
因为浏览器可以并行下载文件(仍然按顺序执行),所以五个1MB的文件比一个5MB的文件下载速度更快。请参考https://dev59.com/E1PTa4cB1Zd3GeqPn_Zt#4878038 - Ruan Mendes
显示剩余6条评论
5个回答

30

这取决于您的应用程序大小以及您要将其交付给谁(我指的是预期设备等),以及您在服务器端可以执行多少工作来确保针对正确的设备(对于大多数非企业人员而言,这仍然远未达到100%可行)。

构建应用程序时,“类”可以快乐地存在于它们自己的文件中。
当将应用程序分割成文件或处理假定太多的构造函数的类(例如实例化其他类)时,循环引用或死结引用 ARE 是一个重大问题。
有多种模式可解决此问题,但最好的方法当然是考虑 DI /IoC 来构建您的应用程序,以便避免循环引用。
您还可以查看 require.js 或其他依赖项加载器。 您需要变得多么复杂取决于应用程序的大小以及您希望所有内容保持多私密。

在为应用程序提供服务时,提供 JS 的基线是将您需要的所有脚本连接在一起(如果您要实例化假定其他东西存在的东西,则按正确顺序),并将它们作为页面底部的一个文件提供。

但那是基线。
其他方法可能包括“懒/延迟”加载。
加载所有需要使页面工作的东西。
同时,如果您有不需要在页面加载时完全使用其功能的小应用程序或小部件,并且实际上,它们需要用户交互或需要一定时间延迟才能执行任何操作,则将这些小部件的脚本加载设置为延迟事件。 当用户按下选项卡时加载选项卡小部件的脚本。 现在您只加载了所需的脚本,并且仅在需要时加载,没有人会真正注意到下载时的微小延迟。

与试图将 40,000 行应用程序塞入一个文件的人进行比较。
仅有一个 HTTP 请求和一个下载,但解析/编译时间现在成为可察觉的秒数分数。

当然,懒加载不是将每个类放在自己的文件中的借口。此时,您应该将它们打包到模块中,并提供运行整个小部件/ applet /等的文件(除非还有其他逻辑位置,在那里功能直到稍后才需要,并且隐藏在进一步的交互后面)。 您也可以在计时器上放置这些模块的加载。预先加载基线应用程序内容(再次在页面底部的一个文件中),然后设置半秒钟左右的超时时间,并加载其他JS文件。现在您不会妨碍页面的操作或用户移动的能力。当然,这是最重要的部分。

2
我是这样的。对于那些没有简单答案的问题,我喜欢回答长篇大论的回答。 - Norguard
@stepanian 类也在路上。模块导入、HTML 模板、HTML 封装/封装(Shadow DOM)、原生 Promise 支持、生成器、更好的列表理解也是如此……它们并非都可以立即使用,鉴于数据驱动、服务/实体为基础的设计的优点以及 ORM 和严格类型化类对该范例的帮助不大,我不太可能接触 JS 类……但它们很快就会出现。 - Norguard
@Norguard 在 JavaScript 中使用“class”一词会给所有人带来不必要的困惑。没有人与您就 JavaScript 作为面向对象语言的能力进行辩论。它只是不属于基于类的语言,也没有类。我很确定这是设计上的决定而不是必然的缺陷。 - stepanian
@stepanian 我建议阅读 ECMA-262 rev.6 草案的第14.5节。ECMAScript 6确实会有类。就像 class MyClass extends MyParent { } 这样的类。我对此的感受放在一边,我在示例中使用类是为了语义上让人们理解“模块化可重用性的不同部分”的概念。JS给你做任何事情的无数种方式。如果你把所有东西都包装在IIFEs中,保存在单独的文件中,那么如何在运行时将它们连接起来?什么是唯一的标准方法?没有。熟悉C++/Java/PHP/Python/Ruby等语言的人都能理解。 - Norguard
@stepanian 在我与主要使用Java或C#的人们的经验中,那些程序员如果发现自己被投入到JS项目中,他们会立即倾向于理解function MyClass(a) { this.member = a; this.method = function () {}; } var myClass = new MyClass();是JS提供的最接近类的东西,因此是他们应用结构的基础,如果他们没有其他JS经验。人们似乎在thisnew中找到了安慰。此外,答案是针对一个问题量身定制的,如果您重新阅读一下... - Norguard
显示剩余5条评论

27

2020年更新:从互联网标准来看,本答案已经非常陈旧,与当今情况相去甚远,但是它仍然偶尔获得投票,因此我觉得有必要提供一些关于自发布以来发生变化的提示。支持async脚本加载、HTTP/2服务器推送功能以及多年来对加载过程的通用浏览器优化,都对将Javascript分解为多个文件对加载性能的影响产生了影响。

对于那些刚开始学习Javascript的人,我的建议仍然不变(使用打包工具/缩小工具,并信任其默认情况下会做正确的事情),但对于任何发现这个问题的人,他们都有更多的经验,我邀请他们探索async加载和服务器推送带来的新功能。

2013年左右的原始回答:


由于下载时间,您应该尝试使您的脚本成为一个单独的大文件。然而,如果您使用缩小工具(您应该使用缩小工具),它们可以为您将多个源文件合并为一个文件。因此,您可以继续在多个文件上工作,然后将它们缩小为一个文件进行分发。

主要例外是公共库,如jQuery,您应该始终从公共CDN加载它们(更有可能用户已经加载它们,因此不需要再次加载)。如果您确实使用了公共CDN,则一定要有一个回退以从自己的服务器加载。

如评论中所述,真相略有复杂;

脚本可以同步加载(<script src="blah"></script>)或异步加载(s=document.createElement('script');s.async=true;...)。同步脚本阻止加载其他资源直到它们加载完成。例如:

<script src="a.js"></script>
<script src="b.js"></script>

会请求 a.js 并等待其加载,然后再加载 b.js。在这种情况下,将 a.js 与 b.js 结合并一次性加载它们显然更好。

同样地,如果 a.js 中有用于加载 b.js 的代码,无论它们是异步还是同步,您都将面临相同的情况。

但是,如果您同时异步加载它们,并根据客户端与服务器之间连接的状态以及许多只能通过分析来确定的考虑因素,这可能会更快。

(function(d){
    var s=d.getElementsByTagName('script')[0],f=d.createElement('script');
    f.type='text/javascript';
    f.async=true;
    f.src='a.js';
    s.parentNode.insertBefore(f,s);
    f=d.createElement('script');
    f.type='text/javascript';
    f.async=true;
    f.src='b.js';
    s.parentNode.insertBefore(f,s);
})(document)

虽然更为复杂,但是可以同时加载 a.js 和 b.js,而不会相互阻塞或影响其他内容。最终,async 属性将会得到更好的支持,届时你就可以像同步加载一样轻松地完成此操作了。不过这需要时间。


1
@JaredFarrish JavaScript 不应该放在 HTML 中,因为它通常在同一站点的页面之间共享,并且您只希望用户下载一次。此外,Emissary:两者都发挥作用。它确实需要更长的下载时间,因为它需要进行更多的往返,但同时连接也可能是一个问题。除非您异步加载脚本,否则浏览器将阻止加载任何内容(包括其他脚本),直到下载完成,因此在一般情况下它们是一个接一个地加载的。 - Dave
规则是用来打破的。在想象我所说的内容时要稍微灵活一些,尝试看到这不仅是一个规则,而且是一个最佳实践和一个所有人都应该考虑并决定如何做的有效因素。 - Jared Farrish
很遗憾,你完全错了。Sync JS不再阻止加载其他资源。这是2006年的信息。在2008年,所有浏览器供应商都从阻止切换到预加载(Microsoft是第一个!)。此外,所有浏览器都从2个http请求一次切换到了6个。因此,大多数开发人员高估了往返时间。如果有3个下载插槽处于连接/等待状态(往返),则另外3个下载插槽可以充分利用连接容量。现在有时最好拆分大型重要文件。规则是用来打破的! - alexander farkas
@alexanderfarkas 很有趣(并且通过使用Chrome的网络工具进行了一些快速测试得到了确认)。显然,我的信息已经过时了。尽管如此,我似乎仍然可以在我的特定(适度小型)脚本中获得更好的性能,一旦它们被合并而不是单独引用(虽然我的测试并不完全科学!)。所以这又是一个答案:基准测试(像往常一样)。 - Dave
@dave 这取决于你的脚本大小。有5-8个以上的脚本是过多的。因此,您必须始终捆绑它们,但大多数情况下不是捆绑到一个文件中,而是2-3个文件,具体取决于每个文件的大小。我经常使用jQuery文件大小作为大小阈值。如果一个文件变得越来越大,我就开始拆分,如果它更小,我就连接起来。我认为对于阻止内容(样式+同步js在头部),这更加重要,因为您可以利用所有6个插槽来阻止内容并减少白屏时间。如果浏览器在用户看不到任何内容时已经开始下载内容图像,那就不太好了…… - alexander farkas
显示剩余4条评论

4
这里有两个问题:a)开发的便利性;b)在下载JS资源时客户端性能。
就开发而言,模块化从来不是坏事。你还可以使用Javascript自动加载框架(如requireJSAMD)来帮助管理您的模块及其依赖关系。
然而,为了解决第二个问题,最好将所有Javascript合并到一个文件中,并进行缩小,以便客户端不会花费太多时间下载所有资源。也有工具(requireJS)可以让您这样做(即将所有依赖项合并到单个文件中)。

0

这取决于您现在使用的协议。如果您正在使用http2,我建议您拆分js文件。如果您使用http,则建议您使用压缩的js文件。

这是一个使用httphttp2的网站示例。

谢谢,希望能帮到您。


虽然这个链接可能回答了问题,但最好在此处包含答案的必要部分,并提供参考链接。仅有链接的答案如果链接页面发生更改,则可能变得无效。 - Divyang Desai

-4

这并不重要。如果您在多个文件中使用相同的JavaScript,那么拥有一个从中获取JavaScript的文件肯定是很好的。这样,您只需要从一个地方更新脚本即可。


你是在建议所有JavaScript都使用一个JS文件吗?这很重要。 - Ruan Mendes
如果您查看jQuery的库,它有很多代码。许多人选择使用整个包而不是分解所需的代码。所以,如果您没有大量流量的网站,请选择一个文件。否则,最好为代码组织结构进行组织。但主要是考虑是否希望在不同文件中使用相同的函数。 :) - Treps

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