Как называется этот шаблон JavaScript и почему он используется?

100

Я изучаю THREE.js и заметил шаблон, в котором функции определены так:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Пример смотрите здесь метод raycast ).

Нормальное изменение такого метода будет выглядеть следующим образом :

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

Сравнивая первую версию с обычным вариантом, кажется, что первая отличается тем:

  1. Он присваивает результат самоисполняющейся функции.
  2. Он определяет локальную переменную внутри этой функции.
  3. Он возвращает фактическую функцию, содержащую логику, которая использует локальную переменную.

Таким образом, основное отличие состоит в том, что в первом варианте полоса назначается только один раз при инициализации, а во втором варианте эта временная переменная создается каждый раз при вызове.

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

Мои вопросы:

  1. Верно ли это предположение?
  2. Есть ли название у этого паттерна?
  3. Почему это используется?
Патрик Клуг
источник
1
@ChrisHayes достаточно честно. Я пометил его как THREE.js, потому что думал, что участники THREE.js наиболее квалифицированы, чтобы ответить на этот вопрос, но да, это общий вопрос JS.
Патрик Клуг
2
Я считаю, что это называется закрытием. Вы можете прочитать о них.
StackFlowed
1
Если это единственное место, где создается Bar, то это одноэлементный шаблон.
Пол
8
Не обязательно для экономии памяти, но он может сохранять состояние между вызовами,
Хуан Мендес
2
@wrongAnswer: не совсем так. здесь анонимная функция (которая будет закрытием) выполняется немедленно.
njzk2

Ответы:

100

Ваши предположения почти верны. Давайте сначала рассмотрим их.

  1. Назначает возврат самоисполняющейся функции

Это называется выражением функции с немедленным вызовом или IIFE.

  1. Он определяет локальную переменную внутри этой функции

Это способ иметь закрытые поля объекта в JavaScript, поскольку он не предоставляет privateключевое слово или функциональность иным образом.

  1. Он возвращает фактическую функцию, содержащую логику, которая использует локальную переменную.

Опять же, главное, что эта локальная переменная является частной .

Есть ли название у этого паттерна?

AFAIK вы можете назвать этот шаблон модульным шаблоном . Цитата:

Шаблон модуля инкапсулирует «конфиденциальность», состояние и организацию с помощью замыканий. Он предоставляет способ объединения общедоступных и частных методов и переменных, защищая части от утечки в глобальную область видимости и случайного столкновения с интерфейсом другого разработчика. С помощью этого шаблона возвращается только общедоступный API, а все остальное в закрытии остается закрытым.

Сравнивая эти два примера, я могу догадаться, почему используется первый:

  1. Он реализует шаблон проектирования Singleton.
  2. С помощью первого примера можно контролировать способ создания объекта определенного типа. Одним из близких совпадений с этой точкой могут быть статические фабричные методы, как описано в Эффективной Java.
  3. Это эффективно, если вам каждый раз нужно одно и то же состояние объекта.

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

MD. Сахиб Бин Махбуб
источник
1
+1 за правильное определение паттерна из книги Адди Османи. Вы правы в своем наименовании - это действительно шаблон модуля - между прочим, раскрывающий шаблон модуля.
Бенджамин Грюнбаум
4
Я согласен с вашим ответом, за исключением части, касающейся «никаких нестандартных частных переменных». Все переменные JS имеют лексическую область видимости «из коробки», что является более мощным / общим механизмом, чем «частные» переменные (например, как в Java); поэтому JS поддерживает «частные» переменные как частный случай обработки всех переменных.
Warbo
Я думаю, что под «частными переменными» вы имели в виду частное «поле объекта»,
Ян Рингроуз
1
Просто для подробностей: klauskomenda.com/code/javascript-programming-patterns/#module
Nagaraj Tantri
4
@LukaHorvat: на самом деле, javascript не более "мощный", чем другие языки (я предпочитаю термин выразительный). На самом деле, это менее выразительно, поскольку единственный способ защитить ваши переменные - включить их в функцию, чтобы избежать какой-либо ерунды с повторным использованием переменных. Шаблон «Модуль» является обязательным условием для создания хорошего кода javascript, но это не особенность языка, это скорее печальный обходной путь, позволяющий избежать укусов из-за слабых сторон языка.
Falanwe
11

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

Хотя возможно, что он ограничивает использование памяти, обычно сборщик мусора все равно будет собирать неиспользуемые объекты, поэтому этот шаблон вряд ли сильно поможет.

Этот узор представляет собой особую форму закрытия .

Фэнъян Ван
источник
1
В JS это обычно называется «модуль»
Леша Огоньков
2
Я бы не назвал это «особой формой закрытия» как таковой. Это шаблон, в котором используется закрытие. Название паттерна по-прежнему открыто.
Chris Hayes
4
Неужели ему нужно название? Все должно быть по шаблону? Неужели нам действительно нужна бесконечная систематика вариантов «модульного паттерна»? Разве это не может быть просто «IIFE с некоторыми локальными переменными, который возвращает функцию?»
Dagg Nabbit
3
@DaggNabbit Когда возникает вопрос «как называется этот шаблон»? Да, ему нужно название или убедительный аргумент, что его у него нет. Кроме того, шаблоны существуют не просто так. Я не понимаю, почему вы ругаете их здесь.
Крис Хейс,
4
@ChrisHayes, если ему нужно имя, зачем вам аргументировать, что у него его нет? В этом нет никакого смысла. Если он нужен, его не должно быть. У меня нет проблем с шаблонами, но я не считаю необходимым классифицировать каждую простую идиому как шаблон. Это приводит к ограниченному мышлению («Это шаблон модуля? Правильно ли я использую шаблон модуля?» Или «У меня есть IIFE с некоторыми локальными переменными, которые возвращают функцию, работает ли этот дизайн для меня?»)
Dagg Nabbit
8

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

Замыкание (идентифицируемое функцией внутри функции) гарантирует, что внутренняя функция имеет доступ к переменным внутри внешней функции.

В приведенном вами примере внутренняя функция возвращается (и назначается ей foo) путем выполнения внешней функции, что означает, что она tmpObjectпродолжает существовать в пределах замыкания, и несколько вызовов внутренней функции foo()будут работать с одним и тем же экземпляром tmpObject.

itsmejodie
источник
5

Ключевое различие между вашим кодом и кодом Three.js заключается в том, что в коде Three.js переменная tmpObjectинициализируется только один раз, а затем используется при каждом вызове возвращаемой функции.

Это было бы полезно для сохранения некоторого состояния между вызовами, подобно тому, как staticпеременные используются в C-подобных языках.

tmpObject закрытая переменная, видимая только внутренней функции.

Он изменяет использование памяти, но не предназначен для экономии памяти.

McFedr
источник
5

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

введите описание изображения здесь

В последнем случае метод сложения будет называться Calculator.add ();

Карлодюрсо
источник
0

В приведенном примере первый фрагмент будет использовать один и тот же экземпляр tmpObject для каждого вызова функции foo (), где, как и во втором фрагменте, tmpObject будет каждый раз новым экземпляром.

Одна из причин, по которой мог быть использован первый фрагмент, заключается в том, что переменная tmpObject может использоваться совместно с вызовами foo () без утечки ее значения в область, в которой объявлен foo ().

Не сразу выполняемая версия функции первого фрагмента на самом деле будет выглядеть так:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Однако обратите внимание, что в этой версии tmpObject находится в той же области видимости, что и foo (), поэтому им можно будет управлять позже.

Лучшим способом достижения той же функциональности было бы использование отдельного модуля:

Модуль 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Модуль 2:

var foo = require('./foo');

Сравнение производительности IEF и именованной функции создателя foo: http://jsperf.com/ief-vs- named-function

Кори Нанн
источник
3
Ваш «лучший» пример работает только в NodeJS, и вы не объяснили, чем он лучше.
Бенджамин Грюнбаум
Создание отдельного модуля не «лучше», а просто другое. В частности, это способ сворачивать функции высшего порядка до объектов первого порядка. Код первого порядка, как правило, легче пройти, но, как правило, он более подробный и вынуждает нас уточнять промежуточные результаты.
Warbo
Модули @BenjaminGruenbaum не только в Node, есть много решений для клиентских модулей, например, browserify. Я считаю, что модульное решение «лучше», потому что оно более читабельно, легче отлаживать и более четко определяет, что находится в области видимости и где.
Кори Нунн