模块化客户端JavaScript没有命名空间污染的方法

我正在编写客户端代码,并希望编写多个可以交互的模块化JS文件,同时防止全局命名空间污染。

的index.html

<script src="util.js"></script> <script src="index.js"></script> 

util.js中

 (function() { var helper() { // Performs some useful utility operation } }); 

index.js

 (function () { console.log("Loaded index.js script"); helper(); console.log("Done with execution."); }) 

这段代码很好地将实用函数保存在一个单独的文件中,不会污染全局名称空间。 但是,助手实用程序函数不会被执行,因为“助手”存在于单独的匿名函数名称空间中。

一种替代方法是将所有JS代码放在一个文件中,或者在全局名称空间中使用单个variables,如下所示:

 var util_ns = { helper: function() { // Performs some useful utility operation. }, etc. } 

这两种方法在模块性和干净的命名空间方面都有缺点。

我习惯于在Node.js里工作(服务器端),我可以在另一个Javascript文件中“需要”一个Javascript文件,有效地将util.js绑定注入到index.js命名空间中。

我想在这里做一些类似的事情(但客户端),允许代码写入单独的模块化文件,而不是在全局命名空间中创build任何variables,同时允许访问其他模块(即像公用程序模块)。

这是可行的(没有图书馆等)?

如果不是这样,那么在客户端JS行为更像Node和npm的领域,我意识到requireJS,browserify,AMD和commonJS标准化尝试的存在。 但是,我不确定每个的优缺点和实际用法。

我强烈build议你继续使用RequireJS 。


模块支持方法(不需要/依赖):

 // moduleA.js var MyApplication = (function(app) { app.util = app.util || {}; app.util.hypotenuse = function(a, b) { return Math.sqrt(a * a + b * b); }; return app; })(MyApplication || {}); // ---------- // moduleB.js var MyApplication = (function(app) { app.util = app.util || {}; app.util.area = function(a, b) { return a * b / 2; }; return app; })(MyApplication || {}); // ---------- // index.js - here you have to include both moduleA and moduleB manually // or write some loader var a = 3, b = 4; console.log('Hypotenuse: ', MyApplication.util.hypotenuse(a, b)); console.log('Area: ', MyApplication.util.area(a, b)); 

在这里,您只创build一个全局variables(名称空间) MyApplication ,其他所有东西都“嵌套”到其中。

小提琴 – http://jsfiddle.net/f0t0n/hmbb7/


**我之前在我的项目中使用过的另外一种方法 – https://gist.github.com/4133310但是,无论如何,我开始使用RequireJS时抛弃了所有这些东西&#x3002;

您应该查看browserify ,它将处理一个模块化的JavaScript项目到一个单一的文件。 您可以像在节点中一样使用require

它甚至给了一堆像urlhttpcrypto的node.js库。

附加 :在我看来,browserify的专业是,它只是简单地使用,不需要自己的代码 – 你甚至可以使用你已经写好的node.js代码。 没有任何必要的样板代码或代码更改,并且它与Node.js一样是CommonJS兼容的。 它输出一个.js ,允许你在你的网站代码中使用require

这有两个缺点,恕我直言:首先,由browserify编译的两个文件如果包含在同一个网站代码中,可以覆盖它们的require函数,所以你必须小心。 另一个是你必须每次运行浏览器来修改代码。 当然,模块系统代码总是编译文件的一部分。

我强烈build议你尝试一个构build工具。

构build工具将允许您在开发时拥有不同的文件(即使在不同的文件夹中),并在最后连接它们以进行debugging,testing或生产。 更好的是,您不需要将库添加到项目中,构build工具驻留在不同的文件中,并且不包含在发布版本中。

我使用GruntJS ,基本上它是这样的。 假设你有util.jsindex.js (需要定义helper对象),都在js目录中。 你可以分开开发,然后连接到dist目录中的一个app.js文件,这个文件将会被你的html加载。 在Grunt中你可以指定类似于:

 concat: { app: { src: ['js/util.js', 'js/index.js'], dest: 'dist/app.js' } } 

这将自动创build文件的连接。 另外,你可以将它们缩小,将它们粘在一起,然后对它们进行任何处理。 您也可以将它们放在完全不同的目录中,并以正确的顺序结束与您的代码打包的一个文件。 您甚至可以在每次保存文件时触发该过程以节省时间。

最后,从HTML,你只需要引用一个文件:

 <script src="dist/app.js"></script> 

添加驻留在不同目录中的文件非常简单:

 concat: { app: { src: ['js/util.js', 'js/index.js', 'js/helpers/date/whatever.js'], dest: 'dist/app.js' } } 

而你的html仍然只会引用一个文件。

其他一些可用的工具是Brunch和Yeoman 。

——–编辑———–

要求JS(和一些替代品,如Head JS)是非常受欢迎的AMD(asynchronous模块定义),它允许简单地指定依赖关系。 另一方面,构build工具(例如Grunt)允许pipe理文件并添加更多function,而不依赖于外部库。 在某些情况下,你甚至可以同时使用两者。

我认为把文件的依赖关系/目录问题/构build过程从你的代码分离出来是很有必要的。 使用构build工具,您可以清晰地查看代码,并可以指定完全独立的位置,指定如何处理文件。 它还提供了一个非常可扩展的体系结构,因为它可以通过结构更改或将来的需求(如包含LESS或CoffeeScript文件)工作。

最后一点,在生产中使用单个文件也意味着更less的HTTP开销。 请记住,尽量减less对服务器的调用数量是很重要的。 有多个文件是非常低效的。

最后, 这是关于AMD工具构build工具的一篇很棒的文章 ,值得一读。

所谓的“全球命名空间污染”大大超过了评级的问题。 我不知道node.js,但在典型的DOM中,默认情况下有数百个全局variables。 名称重复很less是明智select名称的问题。 使用脚本添加一些将不会有丝毫的区别。 使用如下模式:

 var mySpecialIdentifier = mySpecialIdentifier || {}; 

意味着添加一个可以作为你所有代码的根的variables。 然后,您可以将模块添加到您的心脏的内容,例如

 mySpecialIdentifier.dom = { /* add dom methods */ } (function(global, undefined) { if (!global.mySpecialIdentifier) global.mySpecialIdentifier = {}; /* add methods that require feature testing */ }(this)); 

等等。

您也可以使用“扩展”function来testing和添加基础对象,因此您不会复制该代码,并且可以轻松地从不同的文件向基础库对象添加方法。 您的图书馆文档应该告诉您,如果您在名称或function复制之前成为问题(testing也应该告诉你)。

您的整个库可以使用一个全局variables,并可以根据需要轻松扩展或修剪。 最后,你不依赖于任何第三方代码来解决一个相当微不足道的问题。

你可以这样做:

 -- main.js -- var my_ns = {}; -- util.js -- my_ns.util = { map: function () {} // .. etc } -- index.js -- my_ns.index = { // .. } 

这样你只占用一个variables。

解决这个问题的一个方法是让你的组件使用“消息总线”相互交谈。 消息(或事件)由类别和有效载荷组成。 组件可以订阅某个类别的消息,并可以发布消息。 这很容易实现,但也有一些开箱即用的解决scheme。 虽然这是一个很好的解决scheme,但它对应用程序的体系结构也有很大的影响。

这是一个示例实现: http : //pastebin.com/2KE25Par

http://brunch.io/应该是最简单的方法之一,如果你想在你的浏览器中编写类似节点的模块化代码,而不是asynchronousAMD地狱&#x3002; 有了它,你也可以require()你的模板等,而不只是JS文件。

有很多骨架(基础应用程序),你可以使用它,它是相当成熟的。

查看示例应用程序https://github.com/paulmillr/ostio查看一些结构&#x3002; 正如你可能注意到的,它是用咖啡书写的,但是如果你想用js写的话,你可以 – brunch不关心langs。

我想你想要的是https://github.com/component/component

它和Node.js是同步的CommonJS,它的开销要小得多,而且是由编写连接和expression的visionmedia编写的。