Подписывайтесь на мой твиттер, там всегда что-нибудь интересное!

Понимаем немедленно вызываемые функции IIFE и немного больше

В этой статье вы подробно узнаете, что такое IIFE, какие они бывают и как их использовать в JavaScript.

Перевод статьи Essential JavaScript: Mastering Immediately-invoked Function Expressions

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

Один из часто используемых паттернов, связанных с функциями имеет причудливое название: Immediately-invoked Function Expression или по-русски Немедленно вызываемое функциональное выражение. Или, как с любовью говорят в народе — IIFE, произнося, как “Ифи”.

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

Объявление функции обычным способом

Новички в JavaScript обычно чувствуют себя комфортно, имея дело с синтаксисом функций.

function sayHi() {
    alert("Hello, World!");
}

sayHi();  // Покажет "Hello, World!" в alert браузера.

Строки 1–3 объявляют функцию sayHi.

На 5 строке мы вызываем её с обычными скобками ().

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

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

Функциональные выражения

Тут уже все становится интереснее. Давайте посмотрим на то, как выглядят функциональные выражения в JavaScript.

var msg = "Hello, World!";
var sayHi = function() {
    alert(msg);
};

sayHi();  // Покажет "Hello, World!" в alert браузера.

Этот вроде бы простой пример может помочь вам прокачать скиллы в JavaScript до следующего уровня.

На первой строке объявляется переменная msg и ей назначается строковое значение.

Строки 2–4 объявляют переменную sayHi и назначают ей значение, являющееся функцией.

На шестой строке мы вызываем эту самую функцию sayHi.

Первую строку легко понять. Но когда разработчики видят строки 2–4 в первый раз, то это обычно вызывает затруднения, если они зашли из другого языка программирования, например из Java.

Обычно, в строках 2–4 мы указываем значение, которое является функцией для переменной sayHi.

В примере выше, функция с левой стороны оператора назначения зачастую называется “функциональным выражением”. Они буквально везде в JavaScript. Большинство колбэков, которые вы бы написали, были бы этими самыми функциональными выражениями.

Вы возможно уже использовали их, но без понимания того, как это работает под капотом. Но поняв принцип их работы, вы обретёте некую скрытую суперсилу, доступную в JavaScript.

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

Анонимные функциональные выражения

Итак, вы уже знаете, что это такое. Пример выше и был анонимным функциональным выражением. Анонимными они являются потому, что у них не указано имя после слова function.

Проименованные функциональные выражения

Да, функциональные выражения могут иметь имена. Самое обыденное и всюду объясняемое использование проименованных функциональных выражений — это рекурсия. Пока что не беспокойтесь о них, вы вполне можете понять IIFE и так.

var fibo = function fibonacci() {
    // тут вы можете использовать "fibonacci()"
};

 // fibonacci() не сработает, а fibo() да.

Разница тут в том, что функциональное выражение имеет имя “fibonacci”, которое можно использовать внутри самого выражения рекурсивным способом. (Тут вообще есть много всего интересного, например то, что имя функции всплывает в стектрейсе и т.п., но об этом нам не стоит беспокоиться в этом руководстве).

Так, всё! Показывайте мне уже IIFE или я сваливаю!

Спасибо за терпение при объяснении этого важнейшего скилла, который так важен в JavaScript.

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

!function() {
    alert("Hello from IIFE!");
}();
// Покажет alert “Hello from IIFE!”

Это, друзья мои, наша ненаглядная IIFE функция в действии. Когда вы скопируете этот код и вставите его в консоль браузера, вы увидите alert из второй строчки кода. Вот и всё, собственно. Никто никогда не увидит этот alert снова.

Это функция, которая исчезает сразу же после того, как увидит свет.

А теперь давайте разберем этот не совсем интуитивно понятный синтаксис: я знаю, вы обратили внимание на “!”, если же нет, то не переживайте, вы обратили внимание на это сейчас.

Как мы видели до этого, объявление функции всегда начиналось со слова function. Всякий раз, когда JavaScript видит слово function, как вводное слово, он ожидает того, что сейчас будет объявлена функция. Чтобы этого не случилось, мы префиксим “!” перед словом function на первой строке. Это просто подталкивает JavaScript рассматривать всё, что идёт после восклицательного знака, как выражение.

Но самое интересное происходит на 3 сроке, где мы уже мгновенно вызываем это выражение.

Итак, у нас есть функциональное выражение, которое сразу же вызывается после создания. И это, друзья мои, называется IIFE, не смотря на стилистику исполнения, используемую для достижения эффекта.

Вообще, в такой вариации можно использовать не только “!”, а заменить его на + или ~. В общем, подойдет любой унарный оператор.

А теперь, попробуйте это всё в консоли. Поиграйтесь с IIFE в своё удовольствие.

Единственное, что тут делает символ !, так это превращает функцию в выражение, вместо объявления функции. И потом, сразу же, мы выполняем эту функцию.

Ещё один быстрый пример:

void function() {
    alert("Hello from IIFE!");
}();

И снова, void просто принуждает функцию к тому, чтобы ее рассматривали как выражение.

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

Но что делать, если вам нужно его получить и ещё потом где-то использовать? Читайте дальше, чтобы увидеть ответ на этот вопрос.

Классический стиль IIFE

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

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

Сначала давайте посмотрим на ещё один пример функционального выражения!

(function() {
    alert("I am not an IIFE yet!");
});

В примере выше функциональное выражение на строках 1–3 обернуто в скобки. Пока что, это не IIFE, так как это функциональное выражение никогда не запускалось и не запуститься. А теперь давайте доделаем этот код и превратим его в IIFE, для этого у нас есть два варианта стилизации:

// Variation 1
(function() {
    alert("I am an IIFE!");
}());

// Variation 2
(function() {
    alert("I am an IIFE, too!");
})();

Теперь у нас есть две рабочие IIFE. Может быть немного тяжеловато подметить разницу между ними. Так что давайте я вам сейчас её объясню.

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

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

Оба вариант широко используют. Лично я предпочитаю первый. Если мы начнем придираться и капнем глубже, то оба варианта слегка отличаются друг от друга в плане работы под капотом. Но для практических целей и для того, чтобы хоть как-то сократить этот туториал, то скажу я вам так — используйте просто любой из этих способов на ваше усмотрение.

Давайте снова разберем всё на рабочих примерах. Мы начнем с раздачи имён нашим IIFE, так как использование анонимных функций никогда не было хорошей идеей в этом случае.

// Правильная IIFE
(function initGameIIFE() {
     // Весь код тут
}());

 // Дальше будут два неправильных примера
function nonWorkingIIFE() {
 // Теперь понятно зачем нужны эти скобки вокруг меня
 // Без них я объявление функции, а не выражение
 // Тут вы схватите синтаксическую ошибку
}();

function () {
     // И тут тоже
}();

Теперь вы знаете почему странные обертывающие скобки вокруг функционального выражения нужны в паттерне IIFE.

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

IIFE и приватные переменные

В чем действительно хороши IIFE, так это в возможности создания области видимости.

Любые переменные внутри IIFE не видимы для внешнего мира.

Давайте посмотрим на пример.

(function IIFE_initGame() {
     // Приватные переменные к которым нет доступа за пределами IIFE
    var lives;
    var weapons;
    
    init();

     // Приватные функции к которым нет доступа за пределами IIFE
    function init() {
        lives = 5;
        weapons = 10;
    }
}());

В этом примере мы объявили две переменные внутри IIFE и тут они приватные, то есть только для самой IIFE. Никто за пределами IIFE не имеет к ней доступа. Так же, у нас есть функция init, к которой ни у кого нет доступа за пределами IIFE. Но функция init имеет доступ к приватным переменным.

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

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

IIFE с отдаваемым значением

Если вам не нужно отдавать никаких значений в IIFE, то тогда вы могли бы просто использовать первый вариант стилизации, увиденный в этой статье, подразумевающий использование унарных операторов !+void и т.п.

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

var result = (function() {
    return "From IIFE";
}());

alert(result); // alerts "From IIFE"

В этой вариации у нас есть IIFE, которая отдаёт значение на второй строке.

Когда мы выполняем код выше, то строка 5 показывает alert с отдаваемым значением от IIFE.

В общем, тут мгновенно выполнилась IIFE и потом отдало значение, которое назначилось переменной result.

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

IIFE с параметрами

IIFE не только могут отдавать значения, но ещё и брать аргументы во время своего вызова. Давайте посмотрим на этот короткий пример.

(function IIFE(msg, times) {
    for (var i = 1; i <= times; i++) {
        console.log(msg);
    }
}("Hello!", 5));

В примере выше, а именно на 1й строке, IIFE чисто формально имеет два параметра с именами msg и times.

Когда мы выполняем её на 5й строке, то вместо пустых скобок, мы в них передаём аргументы IIFE.

Строки 2 и 3 используют эти параметры уже внутри IIFE.

Это тоже довольно полезный паттерн и мы часто его видим в jQuery, да и в других библиотеках тоже.

(function($, global, document) {
    // используем баксик для jQuery, а global для window
}(jQuery, window, document));

В пример выше, мы передаём jQuerywindow и document, как аргументы к IIFE на строке 3. Код внутри IIFE может ссылаться к ним, как к $globaldocument.

Вот несколько преимуществ такой передачи параметров к IIFE.

JavaScript всегда делает поиск по области видимости заданной функции и продолжает поиск в области видимости выше, пока не найдёт указанный идентификатор. Когда мы передаём document на 3 строке, то это один и единственный раз, когда мы делаем поиск вне локальной области видимости. Любые отсылки к document в IIFE, никогда не должны искаться за пределами локальной области видимости самой IIFE. Так же и с jQuery. Даже в зависимости от сложности кода, рост производительности может быть не слишком высок, но всё равно эту фишку полезно знать.

Также, минифаеры JS могут безопасно минифицировать имена параметров, указанных в функции. Если мы не передавали эти параметры, то минифаеры не будут отсылаться к document или jQuery, так как они находятся вне области видимости этой функции.

Классический модульный паттерн в JavaScript

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

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

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

var Sequence = (function sequenceIIFE() {
    
    // приватная переменная для хранения значения счетчика
    var current = 0;
    
    // объект, возвращаемый IIFE
    return {
    };
    
}());

alert(typeof Sequence); // alerts "object"

В этом примере, наша IIFE отдаёт объект. Смотрите строки 7 и 8.

Также тут есть локальная переменная под названием current.

Отдаваемое IIFE значение, которое является объектом и назначается на переменную Sequence.

А теперь давайте усложним код, добавив несколько функций в отдаваемый нами объект.

var Sequence = (function sequenceIIFE() {
    
    // приватная переменная для хранения значения счетчика
    var current = 0;
    
    // объект, возвращаемый IIFE
    return {
        getCurrentValue: function() {
            return current;
        },
        
        getNextValue: function() {
            current = current + 1;
            return current;
        }
    };
    
}());

console.log(Sequence.getNextValue()); // 1
console.log(Sequence.getNextValue()); // 2
console.log(Sequence.getCurrentValue()); // 2

В этом примере мы добавили две функции нашему отдаваемому объекту.

Строки 8–10 хранят функцию getCurrentValue, которая отдаёт значение из переменной current.

Строки 12–15 добавляют функцию getNextValue, которая увеличивает значение current на 1 и затем отдаёт его значение.

Так как переменная current является приватной в IIFE, то никто кроме функций, имеющих доступ к IIFE через замыкание, не имеет к ней доступа.

Теперь вы узнали реально мощный паттерн JavaScript. Он включает в себя мощь использования IIFE и замыканий вместе.

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

Когда можно пренебречь скобками

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

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

var result = function() {
    return "From IIFE!";
}();

В примере выше, слово function это не первое слово в объявлении. Поэтому JavaScript не воспринимает его, как указание функции. Также есть и другие случаи, в которых вы можете избежать кавычек, когда вы знаете то, что это выражение.

Но даже в этом случае я пытаюсь использовать скобки. Они улучшают читабельность, прямо с первых строк стилистически подсказывая человеку, что это будет IIFE. И никому не надо будет скроллить к последней строке функции, чтобы понять то, что только что они прочитали не простую функцию, а именно IIFE.

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

Ну всё, теперь вы, можно сказать, что сертифицированный IIFE ниндзя!