【问题标题】:Approaches to modular client-side Javascript without namespace pollution没有命名空间污染的模块化客户端 Javascript 的方法
【发布时间】:2012-11-22 22:38:58
【问题描述】:

我正在编写客户端代码,并希望编写多个模块化的 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 代码放在一个文件中或使用全局命名空间中的单个变量,如下所示:

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 标准化尝试。但是,我不确定每种方法的优缺点和实际用法。

【问题讨论】:

标签: javascript node.js


【解决方案1】:

我强烈建议您继续使用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 时,我把所有这些东西都扔掉了。*

【讨论】:

  • 您正在重新声明 MyApplication,这似乎是个问题。我想你需要知道首先加载哪个文件,所以有一个引导程序可以让你加载模块并从那里开始?
  • 每次匿名函数检索MyApplication作为参数时,它都会扩展并返回这个对象。所以它工作正常。
  • RequireJS 在我看来是一种规范的方法,直到 ES6 实现原生模块。这个答案将受益于有关使用 RequireJS 的更多详细信息。
  • RequireJS 有详细的示例和文档。所以你可以访问官方网站并在那里阅读,所以我不需要在这里复制粘贴。 :)
【解决方案2】:

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

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

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

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

【讨论】:

  • 您能否提一下可以证明您的建议的 browserify 的优缺点? browserify 在 commonJS 标准化工作方面的立场如何?
【解决方案3】:

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

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

我使用GruntJS,基本上它是这样工作的。假设你有你的 util.jsindex.js(需要定义帮助对象),都在一个 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 是其他一些可用的工具。

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

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

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

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

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

【讨论】:

  • 感谢您提供详细信息。这听起来很有用。
  • 是的,试试看 :) 我只是在这里触及了表面,但恕我直言,它们对于保持干净的结构化代码非常必要,尤其是在大型应用程序中。
  • 这与 RequireJS 方法相比如何,RequireJS 方法似乎是基于 upvotes 的流行方法?
  • 嘿,我添加了对问题的编辑。另请查看这篇文章:tomdale.net/2012/01/amd-is-not-the-answer
【解决方案4】:

所谓的“全局命名空间污染”被严重高估为一个问题。我不知道 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));

等等。

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

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

【讨论】:

  • 这个实用且简单的解决方案+1 - 但绝对有效
  • -1 告诉人们不要关心全局变量。不会影响你的代表,我会问心无愧的。此外,AMD 似乎更适合解耦。
  • @naugtur——不担心代表。我没有说不关心,只是不要偏执。使用myLib.foo 之类的“命名空间”与myLib_foo 发生名称冲突的可能性完全相同。并且以一种或另一种方式在性能上没有明显差异。使用对象对事物进行分组更加整洁和方便。
  • 你的例子的闭包部分是做什么的?另外,匿名函数中的“全局”和“未定义”参数从何而来?
  • @VinnyD—啊,我的错,忘记传递引用全局对象的this。 “闭包”部分只是一个立即调用的函数表达式 (IIFE),在这种情况下,闭包只包含 globalundefined,但可以包含更多。
【解决方案5】:

你可以这样做:

-- main.js --

var my_ns = {};

-- util.js --

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

-- index.js --

my_ns.index = {
    // ..
}

这样你只占用一个变量。

【讨论】:

  • 是的,在全局命名空间中有一个变量是我提到的一种折衷方案,并且可能是最简单的方法。谢谢!
【解决方案6】:

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

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

【讨论】:

  • 这是一个很棒的通用 JS 设计模式。谢谢!
【解决方案7】:

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

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

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

【讨论】:

    【解决方案8】:

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

    它是同步的 CommonJS 就像 Node.js, 它的开销要少得多, 它是由编写连接和表达的 visionmedia 编写的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-01-03
      • 2011-06-16
      • 2015-03-05
      • 2011-06-04
      • 1970-01-01
      • 2014-02-24
      • 1970-01-01
      相关资源
      最近更新 更多