11

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

索引.html

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

实用程序.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 代码放在一个文件中,或者在全局命名空间中使用单个变量,如下所示:

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

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

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

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

这是否以简单的方式可行(没有库等)?

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

4

8 回答 8

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 时,我扔掉了所有这些东西。*

于 2012-11-22T22:42:44.467 回答
7

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

它甚至提供了一堆 node.js 库,比如url,httpcrypto.

补充:在我看来,browserify 的优点在于它易于使用,不需要自己的代码——你甚至可以使用你已经编写好的 node.js 代码。根本不需要样板代码或代码更改,它与 node.js 一样符合 CommonJS。它也输出一个.js允许您require在您的网站代码中使用的单曲。

恕我直言,这有两个缺点:首先,如果它们包含在同一网站代码中,则由 browserify 编译的两个文件可以覆盖它们的require功能,因此您必须小心。另一个当然是您每次都必须运行 browserify 才能更改代码。当然,模块系统代码始终是编译文件的一部分。

于 2012-11-22T22:43:01.083 回答
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));

等等。

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

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

于 2012-11-22T23:05:25.070 回答
6

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

构建工具将允许您在开发时拥有不同的文件(甚至在不同的文件夹中),并在最后将它们连接起来以进行调试、测试或生产。更好的是,您不需要在项目中添加库,构建工具位于不同的文件中,并且不包含在您的发布版本中。

我使用GruntJS,基本上它是这样工作的。假设你有你的util.jsindex.js(需要定义帮助对象),都在一个js目录中。您可以分别开发两者,然后将两者连接到dist目录中的app.js文件,该文件将由您的 html 加载。在 Grunt 中,您可以指定如下内容:

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

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

最后,从 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

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

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

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

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

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

于 2012-12-02T00:05:21.530 回答
4

你可以这样做:

-- main.js --

var my_ns = {};

-- util.js --

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

-- index.js --

my_ns.index = {
    // ..
}

这样你只占用一个变量。

于 2012-11-22T22:50:03.953 回答
4

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

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

于 2012-11-27T00:50:15.087 回答
0

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

有很多骨架(基础应用程序)可供您使用,而且非常成熟。

检查示例应用程序https://github.com/paulmillr/ostio以查看一些结构。正如您可能注意到的,它是用coffeescript 编写的,但是如果您想用js 编写,您可以——brunch 不关心语言。

于 2012-12-03T01:41:38.223 回答
0

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

它和 Node.js 一样是同步的 CommonJS,它的开销要少得多,而且它是由编写 connect 和 express 的 visionmedia 编写的。

于 2013-01-23T22:04:44.877 回答