JavaScript中的“x = x || {}”技术是什么 - 它如何影响这个IIFE?

时间:2012-10-08 02:56:02

标签: javascript syntax iife

首先,伪代码示例:

;(function(foo){

    foo.init = function(baz) { ... }

    foo.other = function() { ... }

    return foo;

}(window.FOO = window.FOO || {}));

这样称呼:

FOO.init();

我的问题:

  • window.FOO = window.FOO || {}
  • 的技术名称/说明是什么?

我理解代码的作用...请参阅下面的我的原因。


询问原因:

我这样称呼全球传递:

;(function(foo){
    ... foo vs. FOO, anyone else potentially confused? ...
}(window.FOO = window.FOO || {}));

...但我只是不喜欢称小写“foo”,因为全局被称为大写FOO ......这看起来很混乱。

如果我知道这种技术的技术名称,我可以说:

;(function(technicalname){
    ... do something with technicalname, not to be confused with FOO ...
}(window.FOO = window.FOO || {}));

我见过最近(很棒)的例子,他们称之为“exports”:

;(function(exports){
    ...
}(window.Lib = window.Lib || {}));

我想我只是想把我的编码惯例标准化......我想了解专业人士做了什么以及他们如何思考(这就是我在这里问的原因)!

4 个答案:

答案 0 :(得分:5)

<子>模式
(function (foo) {
    ...code...
    foo.bar = baz;
    ...more code...
}(window.FOO = window.FOO || {});

您描述的模式没有正式名称,因为它是三个单独的模式组合在一起。每个模式都有多个名称,但对于这篇文章,我将使用以下术语:

  • 闭合
  • 别名
  • 命名空间扩展

封闭

整个模式的基础是closure。它只是一个用于扩展变量和函数的函数,这样它们就不会污染全局命名空间:

没有关闭
//these declare window.foo and window.bar respectively
//as such, they pollute the global namespace
var foo;
function bar() {}
闭包,在本例中为Immediately Invoked Functional Expression (IIFE)
(function () {
    //these declare foo and bar within the function
    //but they are not accessible outside the function
    var foo;
    function bar() {}
}());

将变量保存在闭包中的优点是您不必担心有人会覆盖您正在使用的变量。这对于经常使用的ij等临时变量尤为重要。

别名

此模式的第二个重要部分是别名。别名允许在闭包中定义和使用变量,而无需担心它所驻留的全局命名空间。

没有别名
(function () {
    ...
    foo = window.SomeFunction(bar, baz);
    ...
}());
使用别名
(function (sf) { //local name
    ...
    foo = sf(bar, baz);
    ...
}(window.SomeFunction)); //global namespace

这一点尤其重要,因为这意味着可以通过在单个位置更改名称来跨大型JavaScript文件更改全局命名空间。这是A Good Thing™。此外,minifiers可以将内部别名缩短为单个字母变量名称,例如a,从而在缩小时节省大量字节。

命名空间扩展

命名空间扩展模式依赖于或运算符(||)的合并行为。在许多语言中,&&||都会返回truefalse,但在JavaScript中,&&会返回第一个falsey值({{ 1}},false0''null)和undefined会返回第一个||值(任何不是{{} 1}})。对于两个运算符,如果未找到相应的类型,则返回最后一个参数。这使truthy运算符成为定义新命名空间的便捷方式,只有它不存在

没有命名空间扩展
falsey
使用命名空间扩展
||

这很有用,因为它允许使用其他属性和方法扩展命名空间,而不必担心属性和方法的定义顺序。

在第一个示例中,if (typeof window.Foo === 'undefined') { window.foo = {}; } 需要在window.foo = window.foo || {}; 之前执行:

<子> FileA.js
FileA
<子> FileB.js
FileB

在第二个示例中,window.foo = {}; window.foo.bar = 'baz'; window.foo.fizz = 'buzz'; 可以按任何顺序执行:

<子> File1.js
File1
<子> File2.js
File2

现在一起

使用每个模式一起创建一个非常强大的模块化脚本:

window.foo = window.foo || {};
window.foo.bar = 'baz';

答案 1 :(得分:2)

我一直把它理解为"Null Coalescing"

至于影响您的IIFE,如果已经实例化,则传入window.FOO,如果不实例,则传入空对象。

你也可以把它读成:

window.FOO = window.FOO || {};
;(function(foo){

    foo.init = function(baz) { ... }

    foo.other = function() { ... }

    return foo;

}(window.FOO));

就个人而言,我更喜欢不同的模式:

var FOO;
if (!FOO) {
    FOO = {};
}
(function () {
    "use strict";
    FOO.prop1 = 'bar';
    FOO.bar = function (z) {
        return z + 1;
    };
}());

我发现它不那么令人困惑,并帮助我确保干净的命名空间。

答案 2 :(得分:2)

补充皮特的答案,另一种形式:

;(function() {
  var Foo = window.Foo = window.Foo || {};

  Foo.foo  = 'bar';
  Foo.baz  = function() {
    return "Hello World!";
  };
})();

我通常使用本地var为minifier提供保存几个字节的机会。当你在var MyView = window.MyApp.Views.MyView上的命名空间深处工作时,这会产生更大的影响。

答案 3 :(得分:1)

正如其他人所说,你的第一个问题是完全独立的,与你的第二个问题无关。它们彼此无关,除非您决定将它们组合在一个语句中,而不是在两个语句中进行组合。

对于你的第一个问题,Douglas Crockford称之为默认作业。如同,如果变量存在则保持不变,否则将其初始化为指定的默认值。当你看到类似的东西时:

foo = foo || {};

你的大脑应该把它读作:

foo defaults to `{}`

从技术上讲,如果foo是假的,它真的“将{}分配给foo”但是我们假设当我们使用时,任何虚假的例如零,null或未定义都是foo的无效值这个。此外,单词default已经暗示“如果没有定义foo”。

但是,说到这一点并且知道你的第二个问题,很明显默认并不是一个合适的词,因为传递给你的IIFE的参数名称。该参数正在做的只是将方法和属性附加到传入的对象。在这种情况下, exports 是合适的,因为“这些是对象的公共导出成员”。我认为 attach 也是一个合适的名称,如“将这些东西附加到对象”。我个人的偏好只是将其命名为 obj ,就像在对象中一样,这是您期望传递的东西。