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

Я изучаю 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

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

  • Он назначает возврат самоисполняемой функции

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

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

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

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

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

Есть ли имя для этого шаблона?

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

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

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

  • Он реализует шаблон проектирования Singleton.
  • Можно управлять тем, как объект определенного типа может быть создан с использованием первого примера. Одним из близких совпадений с этой точкой может быть static factory методы, как описано в Эффективной Java.
  • эффективный, если вам нужно одно и то же состояние объекта каждый раз.

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

Ответ 2

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

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

Этот шаблон представляет собой конкретную форму closure.

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

enter image description here

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

Ответ 6

В приведенном примере первый фрагмент будет использовать один и тот же экземпляр 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 creator: http://jsperf.com/ief-vs-named-function