Что такое х = х || Техника {} в JavaScript — и как она влияет на 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 || {}));

Думаю, я просто пытаюсь стандартизировать свои соглашения по кодированию... Я хотел бы узнать, что делают профессионалы и как они думают (вот почему я спрашиваю здесь)!


person mhulse    schedule 08.10.2012    source источник
comment
Вы можете назвать аргумент FOO. Это скроет window.FOO, если вы явно не сошлетесь на него с помощью window.FOO.   -  person icktoofay    schedule 08.10.2012
comment
stackoverflow.com/ вопросы/6439579/   -  person    schedule 08.10.2012
comment
Это один и тот же объект. Не стесняйтесь давать то же имя или другое имя. alert(window.FOO === foo); // true Технического названия нет. Это зависит от его использования. Если вы предоставляете библиотеку, ссылайтесь на нее как на свою библиотеку. Если вы экспортируете конструктор Foo, назовите его конструктором Foo.   -  person I Hate Lazy    schedule 08.10.2012
comment
Спасибо за комментарии, ребята, я очень ценю помощь. @icktoofay Я никогда не думал ссылаться на window.FOO в своем IIFE; Я всегда ссылался на его Alias (спасибо zzzzBov). Итак, использование FOO сработало бы для меня. @pst Спасибо за ссылку! Извините, что я пропустил эту тему. @user1689607 user1689607 Спасибо за разъяснения, я очень ценю это! Спасибо всем за помощь... Я должен вам всем несколько пивоварен Орегона. :)   -  person mhulse    schedule 09.10.2012


Ответы (4)


Pattern
(function (foo) {
    ...code...
    foo.bar = baz;
    ...more code...
}(window.FOO = window.FOO || {});

У шаблона, который вы описываете, нет официального названия, потому что это три отдельных шаблона. Каждый паттерн имеет несколько названий, но в этом посте я буду использовать следующую терминологию:

  • закрытие
  • псевдоним
  • расширение пространства имен

Закрытие

Основой всего узора является closure. Это просто функция, которая используется для определения области видимости переменных и функций, чтобы они не загрязняли глобальное пространство имен:

No closure
//these declare window.foo and window.bar respectively
//as such, they pollute the global namespace
var foo;
function bar() {}
Closure, in this case, an 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() {}
}());

Преимущество хранения переменных внутри замыкания заключается в том, что вам не придется беспокоиться о том, что кто-то перезапишет используемые вами переменные. Это особенно важно для часто используемых временных переменных, таких как i или j.

Псевдоним

Второй важной частью этого шаблона является алиасинг. Псевдоним позволяет определить переменную и использовать ее внутри замыкания, не беспокоясь о том, в каком глобальном пространстве имен она находится.

Without Aliasing
(function () {
    ...
    foo = window.SomeFunction(bar, baz);
    ...
}());
With Aliasing
(function (sf) { //local name
    ...
    foo = sf(bar, baz);
    ...
}(window.SomeFunction)); //global namespace

Это особенно важно, поскольку это означает, что глобальное пространство имен можно изменить в большом файле JavaScript, изменив имя в одном месте. Это хорошая вещь. Кроме того, минификаторы могут сокращать внутренний псевдоним до однобуквенного имени переменной, такого как a, что обеспечивает значительную экономию байтов при минимизации.

Расширение пространства имен

Шаблон расширения пространства имен основан на поведении объединения оператора или (||). Во многих языках && и || возвращают либо true, либо false, но в JavaScript && возвращает первое значение falsey (false, 0, '', null, undefined), а || возвращает первое значение truthy (все, что не falsey). Для обоих операторов, если соответствующий тип не найден, возвращается последний аргумент. Это делает оператор || удобным способом определения нового пространства имен, только если оно еще не существует.

Without namespace extension
if (typeof window.Foo === 'undefined') {
    window.foo = {};
}
With namespace extension
window.foo = window.foo || {};

Это полезно, поскольку позволяет расширить пространство имен дополнительными свойствами и методами, не беспокоясь о том, в каком порядке были определены свойства и методы.

В этом первом примере FileA нужно будет выполнить до FileB:

FileA.js
window.foo = {};
window.foo.bar = 'baz';
FileB.js
window.foo.fizz = 'buzz';

Во втором примере File1 и File2 могут выполняться в любом порядке:

File1.js
window.foo = window.foo || {};
window.foo.bar = 'baz';
File2.js
window.foo = window.foo || {};
window.foo.fizz = 'buzz';

Все вместе сейчас

Использование каждого шаблона вместе создает очень мощный модульный скрипт:

//use foo internally so that you don't have to worry about
//what the global namespace is called
(function (foo) {
    //declare variables internally that you want to keep local to the script
    var i,
        len,
        internal,
        qux;
    //declare functions/properties on the alias when you want to expose them
    foo.bar = function () {...};
//extend the global namespace so that existing extensions are persistent
}(window.FOO = window.FOO || {}));
person zzzzBov    schedule 08.10.2012
comment
Спасибо @zzzzBov! Ваш ответ (и все остальные ответы) действительно помог мне! Единственное, что я мог бы добавить к вашему ответу, это информация Пита о том, что это называется Null Coalescing? Еще раз спасибо! Я очень ценю профессиональную помощь! - person mhulse; 09.10.2012
comment
Я забыл сказать: спасибо, что указали, что я использую три шаблона! Не имел представления. По какой-то причине я предполагал, что просто использую одноэлементный IIFE. (Чувствую себя нубом!) :) - person mhulse; 09.10.2012
comment
@MickyHulse, в некотором смысле оператор || в JavaScript действует аналогично оператору объединения с нулевым значением в C #, но он достаточно отличается, поэтому я бы не назвал его оператором слияния с нулевым значением. Что касается использования нескольких шаблонов, автомобиль обычно соответствует описанному шаблону, но он состоит из множества более мелких шаблонов, таких как оси колес и двери, которые все вместе составляют автомобиль. - person zzzzBov; 09.10.2012
comment
Ааа, теперь вижу. Спасибо @zzzBov! Я очень ценю, что вы (и все остальные тоже) нашли время, чтобы помочь нубу вроде меня. :) - person mhulse; 09.10.2012
comment
Я бы вообще не использовал термин объединение для этого — обычно это называется оценкой короткого замыкания. Если вы знаете, что левый операнд оператора || верен, то значение правого операнда не имеет значения. - person Alnitak; 31.07.2013
comment
@Alnitak, поведение оценки короткого замыкания существует во многих языках, которые используют && и ||, однако в большинстве других языков && и || возвращают логическое значение, а ECMAScript возвращает значения операнда. Вот почему я использовал коалесцирование. - person zzzzBov; 31.07.2013

Я всегда понимал это как "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;
    };
}());

Я считаю, что это менее запутанно и помогает мне обеспечить чистое пространство имен.

person pete    schedule 08.10.2012
comment
Спасибо @pete! Это отличная информация... Null Coalescing - новый для меня термин! Я изучаю это сейчас, большое спасибо за вашу профессиональную помощь и примеры! :) - person mhulse; 09.10.2012

Чтобы дополнить ответ Пита, альтернативная форма:

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

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

Обычно я использую локальный var, чтобы дать минификатору возможность сэкономить несколько байтов. Это имеет больший эффект, когда вы работаете на нескольких уровнях в глубине вашего пространства имен, например, на var MyView = window.MyApp.Views.MyView.

person Renato Zannon    schedule 08.10.2012
comment
Спасибо Ренато! Это очень полезно. Приятно видеть несколько примеров... Я многому учусь у всех вас, потрясающие гуру JS! :) - person mhulse; 09.10.2012

Как уже отмечали другие, ваш первый вопрос полностью отделен и не зависит от вашего второго вопроса. Они не имеют ничего общего друг с другом, за исключением того факта, что вы решили объединить их в одном операторе, а не в двух.

Что касается первого вопроса, Дуглас Крокфорд называет его назначением по умолчанию. Например, если переменная существует, оставьте ее в покое, в противном случае инициализируйте ее указанным значением по умолчанию. Когда вы видите что-то похожее на:

foo = foo || {};

Ваш внутренний взор должен прочитать это как:

foo defaults to `{}`

Технически это действительно "назначить {} для foo, если foo ложно", но мы сделали предположение, что любое ложное значение, такое как ноль, null или undefined, является недопустимым значением для foo, когда мы используем это. Кроме того, слово default уже подразумевает «если foo не определено».

Но, сказав это и зная ваш второй вопрос, очевидно, что default не совсем подходящее слово в качестве имени параметра, переданного в ваш IIFE. Этот параметр просто прикрепляет методы и атрибуты к переданному объекту. В этом случае exports подходит, как в «это общедоступные экспортированные члены объекта». Я бы подумал, что attach также является подходящим именем, например, «прикрепить эти вещи к объекту». Лично я предпочитаю просто называть его obj как объект, который является типом того, что вы ожидаете передать.

person slebetman    schedule 08.10.2012
comment
Спасибо слебетман! Это очень полезно! Извините, что мой первоначальный вопрос был немного неуместен... Я все еще новичок в некоторых из этих передовых методов и концепций. Я очень ценю, что вы (и все остальные, кто ответил) нашли время, чтобы ответить на мои вопросы и научить чему-то новому. :) - person mhulse; 09.10.2012