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

时间:2012-11-22 22:38:59

标签: javascript node.js

我正在编写客户端代码,并希望编写多个可以交互的模块化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.");
})

此代码很好地将实用程序函数保存在单独的文件中,并且不会污染全局命名空间。但是,不会执行辅助实用程序函数,因为'helper'存在于单独的匿名函数名称空间中。

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

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

这两种方法在模块化和干净的命名空间方面都有所不同。

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

我想在这里做类似的事情(但客户端)允许代码在单独的模块化文件中编写,而不在全局命名空间中创建任何变量,同时允许访问其他模块(例如实用程序模块) )。

这是否可以通过简单的方式实现(没有库等)?

如果没有,在使客户端JS的行为更像Node和npm的领域,我知道requireJS,browserify,AMD和commonJS标准化尝试的存在。但是,我不确定每种方法的优缺点和实际用法。

8 个答案:

答案 0 :(得分:17)

我强烈建议您继续使用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));

在这里,您只创建一个全局变量(命名空间)MyApplication,其他所有内容都“嵌套”在其中。

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


**我之前在项目中使用的另一种方法 - https://gist.github.com/4133310 但无论如何,当我开始使用RequireJS时,我抛弃了所有这些东西。*

答案 1 :(得分:7)

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

它甚至提供了一堆node.js库,如urlhttpcrypto

ADDITION :在我看来,浏览器的专业是它只是使用而不需要自己的代码 - 您甚至可以使用已经编写的node.js代码。根本没有必要的样板代码或代码更改,它与node.js一样符合CommonJS标准。它会输出一个.js,允许您在网站代码中使用require

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

答案 2 :(得分:6)

所谓的“全球名称空间污染”被大大超过了一个问题。我不知道node.js,但在一个典型的DOM中,默认情况下有数百个全局变量。名称重复很少是明智地选择名称的问题。添加一些使用脚本不会产生丝毫差异。使用如下模式:

var mySpecialIdentifier = mySpecialIdentifier || {};

表示添加一个可以作为所有代码根的变量。然后,您可以在心脏的内容中添加模块,例如

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

等等。

您还可以使用“扩展”功能来测试和添加基础对象,这样您就不会复制该代码,并且可以轻松地从不同文件向基础库对象添加方法。您的库文档应该告诉您是否在它成为问题之前复制名称或功能(并且测试也应该告诉您)。

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

答案 3 :(得分:6)

我强烈建议您尝试构建工具。

构建工具将允许您在开发时使用不同的文件(即使在不同的文件夹中),并在最后将它们连接起来进行调试,测试或生产。更好的是,您不需要在项目中添加库,构建工具驻留在不同的文件中,并且不包含在您的发行版中。

我使用GruntJS,基本上就像这样。假设您在 js 目录中都有 util.js index.js (需要定义辅助对象)。您可以单独开发两者,然后将它们连接到 dist 目录中的 app.js 文件,该文件将由您的html加载。在Grunt中,您可以指定类似的内容:

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

这将自动创建文件的串联。此外,您可以缩小它们,拖拽它们,并进行任何您想要的过程。您也可以将它们放在完全不同的目录中,最终仍然会以正确的顺序将一个文件与您的代码打包在一起。您甚至可以在每次保存文件时触发该过程以节省时间。

最后,从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仍然只会引用一个文件。

其他一些可用的工具是BrunchYeoman

--------编辑-----------

要求JS(以及一些替代品,例如Head JS)是一个非常流行的AMD(异步模块定义),它允许简单地指定依赖项。另一方面,构建工具(例如,Grunt)允许管理文件并添加更多功能而不依赖于外部库。在某些情况下,你甚至可以同时使用它们。

我认为将文件依赖项/目录问题/构建过程与代码分开是可行的方法。使用构建工具,您可以清楚地看到代码,并在一个完全独立的位置指定如何处理文件。它还提供了一种可扩展的体系结构,因为它可以通过结构更改或将来的需求(例如包括LESS或CoffeeScript文件)来完成。

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

最后,this is a great article on AMD tools s build tools,值得一读。

答案 4 :(得分:4)

你可以这样做:

-- main.js --

var my_ns = {};

-- util.js --

my_ns.util = {
    map: function () {}
    // .. etc
}

-- index.js --

my_ns.index = {
    // ..
}

这样你只占用一个变量。

答案 5 :(得分:4)

解决此问题的一种方法是让您的组件使用&#34;消息总线&#34;相互通信。消息(或事件)由类别和有效负载组成。组件可以订阅特定类别的消息并可以发布消息。这很容易实现,但也有一些开箱即用的解决方案。虽然这是一个简洁的解决方案,但它对应用程序的体系结构也有很大的影响。

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

答案 6 :(得分:0)

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

你可以使用很多骨架(基础应用程序)并且它已经非常成熟。

检查示例应用程序https://github.com/paulmillr/ostio以查看一些结构。正如你可能注意到的那样,它是用coffeescript写的,但如果你想用js写,你可以 - 早午餐并不关心langs。

答案 7 :(得分:0)

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

它就像Node.js一样是同步CommonJS, 它的开销要小得多, 它是由visionmedia撰写的,他写了连接和表达。