这两个JavaScript模式有什么区别吗?

8
看了一些JavaScript库和其他人的代码,我发现有两种常见模式,不知道使用其中一种是否有差异或优势。这些模式看起来像这样:

1.

var app = (function () {
    // Private vars

    // Module
    var obj = {
        prop: "",
        method: function () {}
    };

    return obj;
})();

2.

(function () {
    // Private vars

    // Module
    var obj = {
        prop: "",
        method: function () {}
    };

    window.app = obj;
})();

这些模式是相同的,还是其中一个比另一个更有优势或用途不同呢?
提前感谢。

4
我个人更喜欢第一种,因为它没有“硬编码”对象。 - mplungjan
5个回答

5
第二个假设在父范围中存在一个名为“window”的对象,并在那里分配属性。第一个让调用者进行赋值,并且不依赖于定义了一个“window”(它可能仅在Web浏览器内部存在)。因此,我会说第一个明显更好(更自包含,不依赖于特定的环境)。

1
非常少的ECMAscripts(如果有的话)能够同时兼容浏览器和其他平台。因此,“假定” window 为全局对象并没有那么大的错误。 - jAndy
@jAndy:我不知道,我写了一些既可以在浏览器环境下,也可以在非浏览器环境下工作的脚本。如果你需要全局对象,你可以在不使用window的情况下获取它:var globalObj = (function() { return this; })(); - Tim Down

2

简而言之:选择一种方法并保持一致。


在我看来,第一种方法在可读性方面略有优势。当我阅读时,我会看到“模块app正在被定义”,并且这个闭包内的所有内容都属于该模块。对我来说,这是一种自然的分解方式,并强制了即将定义的模块的面向对象性质。

我更喜欢第一种方法的另一个原因是,将模块定义的作用域更改为其他范围更加清晰。你定义的每个模块都不需要成为全局作用域的一部分。使用第二种方法,如果作用域没有通过传递父对象注入(就像Jared Farrish在他的jQuery示例中所示),那么如果您决定更改该父对象的名称,则可能会破坏您的代码。以下示例说明了这一点:

var namespace = {
  subns: { ... }
};

(function() {
  var module = { ... };
  namespace.subns.someModule = module;
}());

任何时候标识符namespacesubns更改,您都需要更新此模块和任何遵循此模式并将自己添加到同一对象的其他模块。
总的来说,方法一和方法二(使用依赖注入)都没有比另一个“更好”,这只是一个偏好问题。这次讨论可以带来的唯一好处就是您应该选择一种方法并保持一致

1
它们都完成了同样的事情,在代码运行时在全局命名空间中创建一个对象。其中一个不比另一个更“硬编码”,因为两者都没有进行任何类型的函数原型设计,你也无法使用new关键字创建对象的克隆。这只是个人偏好问题。例如,jQuery类似于后者:
(function( window, undefined ) {

// Use the correct document accordingly with window argument (sandbox)
var document = window.document;
var jQuery = (function() {

// Define a local copy of jQuery
var jQuery = function( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );
    },

    // Map over jQuery in case of overwrite
    _jQuery = window.jQuery,

    // Map over the $ in case of overwrite
    _$ = window.$,

...

但是Prototype JS库却做到了前者:

var Prototype = {
  Version: '1.6.1',

  Browser: (function(){
    var ua = navigator.userAgent;
    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
    return {
      IE:             !!window.attachEvent && !isOpera,
      Opera:          isOpera,
      WebKit:         ua.indexOf('AppleWebKit/') > -1,
      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
      MobileSafari:   /Apple.*Mobile.*Safari/.test(ua)
    }
  })(),

...

我不知道哪个比另一个更好,也不知道它们是否以不同的方式完成任务(在窗口命名空间中创建应用程序对象)。


0
在第一个例子中,如果app在另一个函数内定义,app将仅在该局部作用域中可用,而在第二个例子中,app变量被明确分配给全局作用域。
在第二个例子中,只有在全局范围外的函数中定义时,app才会被分配到全局范围。

-2
第二种形式有一个小优点,那就是你拥有一个完全自包含的函数;例如,你可以为你的JS文件设置标准的头部和尾部。
我不完全认同的部分是块内的局部变量。我倾向于使用这种方式:
(function () {
   // Private vars

   // Module
   window.app = {
       prop: "",
       method: function () {}
   };
})();

虽然当你做的不只是一个事情时,比如用多个方法构建一个对象而不是像这个例子中一样只有一个对象时,这种方式就会有些问题。


你能详细说明一下函数自包含的优势在哪里吗?谢谢。 - jamesmortensen
我举了一个例子;其实使用这两种模式中的哪一种是个人偏好问题。我认为没有任何技术原因可以证明其中一种模式更优秀。 - Spyder
1
两种形式都是“完全自包含的”,正如你所说。 - Justin Johnson
真的吗?第二个创建了一个全局变量,而第一个则没有(除非您明确决定要这样做)。这不符合我对“完全自包含”的定义。 - Spyder

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