Немедленно вызываемая функция (Immediately Invoked Function Expression — IIFE) в JavaScript — это конструкция, позволяющая вызывать функцию непосредственно после ее определения.
В этой статье мы разберемся, почему стоит отказаться от использования IIFE, несмотря на ее былые заслуги.
С тех пор как появился стандарт ES6, мы можем объявлять переменные и константы внутри блока с помощью let и const. Вместе с этим стандартом также появилась возможность выделять переменные и константы в автономные блоки, недоступные извне.
Например:
Тогда x не будет доступен извне. Это явно лучше, чем:
Теперь, когда ES6 поддерживают практически все современные браузеры, мы должны прекратить использование IIFE для отделения переменных от внешнего мира. Другой способ изолировать переменные — это модули, с поддержкой которых теперь тоже нет никаких проблем. Пока мы не экспортируем их, они не будут доступны для других модулей.
Замыкание — это механизм, при котором функция запоминает свои внешние переменные и может получить к ним доступ. Если внутри функции мы создадим еще одну и вернем ее, то возвращаемая функция сможет обращаться к внешним переменным, которые являются внутренними для внешней функции.
Например, здесь мы можем получить некоторые побочные эффекты:
Опять же, теперь нет смысла городить весь этот огород, поскольку у нас есть автономные блоки и модули для изоляции данных. Мы можем просто поместить все это в свой собственный модуль, тогда нам не нужно будет беспокоиться о доступе к данным.
Замыкания вызывают побочные эффекты, что не очень хорошо, так как лучшие практики и здравый смысл предписывают нам избегать их, когда это возможно. Они затрудняют тестирование функций, которые в этом случае не являются чистыми.
Кроме того, не стоит плодить вложенные функции, когда этого можно избежать: с ними код становится более запутанным, чем без них.
Лучшая альтернатива — заменять их на модули:
В приведенном выше коде мы имеем такое же объявление переменной count и экспортируем функцию id (), чтобы она была доступна для других модулей. Так мы скрываем count и открываем id (), как и хотели, только без использования IIFE. В результате мы получаем меньшую вложенность и избавляемся от необходимости определять другую функцию и запускать ее.
Мы могли бы написать такое:
Но теперь нет необходимости использовать IIFE для создания алиасов. Используя модули, мы просто можем импортировать переменную под другим именем, тем самым создавая для нее алиас.
И тогда достаточно написать:
Кроме того, не стоит добавлять объекту window новые свойства, так как это загрязняет глобальную область видимости.
С появлением globalThis нам не нужно беспокоиться об имени глобального объекта в различных окружениях, поскольку он становится стандартом.
Мы могли бы использовать IIFE для захвата глобального объекта:
Но теперь в этом нет необходимости. Да и раньше можно было обойтись без этого, просто написав следующую строку:
Если быть более точным, то можно написать вот что:
И тогда можно не добавлять дополнительный вызов функции и вложенность, которые появляются при использовании IIFE.
С модулями JavaScript нам больше не нужно отделять остальной код от IIFE, чтобы выполнить минификацию наших файлов должным образом.
Webpack, Browserify, Parcel, Rollup и так далее могут работать с модулями должным образом, поэтому мы должны использовать их для создания более чистого кода.
Пора перестать использовать IIFE в нашем коде. Это добавляет лишние функции и избыточную вложенность.
Кроме того, сейчас это анахронизм: IIFE применялись еще до появления и широкого распространения практики использования модулей в JavaScript. В 2020 году для разделения кода мы должны использовать модули и автономные блоки.
Для предотвращения доступа извне к переменным, находящимся внутри модуля, мы можем использовать блочные области видимости.
В этой статье мы разберемся, почему стоит отказаться от использования IIFE, несмотря на ее былые заслуги.
Мы можем объявлять переменные внутри автономных блоков
С тех пор как появился стандарт ES6, мы можем объявлять переменные и константы внутри блока с помощью let и const. Вместе с этим стандартом также появилась возможность выделять переменные и константы в автономные блоки, недоступные извне.
Например:
{
let x = 1;
}
Тогда x не будет доступен извне. Это явно лучше, чем:
(()=>{
let x = 1;
})();
Теперь, когда ES6 поддерживают практически все современные браузеры, мы должны прекратить использование IIFE для отделения переменных от внешнего мира. Другой способ изолировать переменные — это модули, с поддержкой которых теперь тоже нет никаких проблем. Пока мы не экспортируем их, они не будут доступны для других модулей.
Мы можем избавиться почти от всех замыканий
Замыкание — это механизм, при котором функция запоминает свои внешние переменные и может получить к ним доступ. Если внутри функции мы создадим еще одну и вернем ее, то возвращаемая функция сможет обращаться к внешним переменным, которые являются внутренними для внешней функции.
Например, здесь мы можем получить некоторые побочные эффекты:
const id = (() => {
let count = 0;
return () => {
++count;
return `id_${count}`;
};
})();
Опять же, теперь нет смысла городить весь этот огород, поскольку у нас есть автономные блоки и модули для изоляции данных. Мы можем просто поместить все это в свой собственный модуль, тогда нам не нужно будет беспокоиться о доступе к данным.
Замыкания вызывают побочные эффекты, что не очень хорошо, так как лучшие практики и здравый смысл предписывают нам избегать их, когда это возможно. Они затрудняют тестирование функций, которые в этом случае не являются чистыми.
Кроме того, не стоит плодить вложенные функции, когда этого можно избежать: с ними код становится более запутанным, чем без них.
Лучшая альтернатива — заменять их на модули:
let count = 0;
export const id = () => {
++this.count;
return `id_${count}`
}
В приведенном выше коде мы имеем такое же объявление переменной count и экспортируем функцию id (), чтобы она была доступна для других модулей. Так мы скрываем count и открываем id (), как и хотели, только без использования IIFE. В результате мы получаем меньшую вложенность и избавляемся от необходимости определять другую функцию и запускать ее.
Мы можем по-другому создавать алиасы для переменных
Мы могли бы написать такое:
window.$ = function foo() {
// ...
};(function($) {
// ...
})(jQuery);
Но теперь нет необходимости использовать IIFE для создания алиасов. Используя модули, мы просто можем импортировать переменную под другим именем, тем самым создавая для нее алиас.
И тогда достаточно написать:
import { $ as jQuery } from "jquery";
const $ = () => {};
Кроме того, не стоит добавлять объекту window новые свойства, так как это загрязняет глобальную область видимости.
Мы можем легко получить глобальный объект
С появлением globalThis нам не нужно беспокоиться об имени глобального объекта в различных окружениях, поскольку он становится стандартом.
Мы могли бы использовать IIFE для захвата глобального объекта:
(function(global) {
// ...
})(this);
Но теперь в этом нет необходимости. Да и раньше можно было обойтись без этого, просто написав следующую строку:
const globalObj = self || window || global;
Если быть более точным, то можно написать вот что:
const getGlobal = () => {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
И тогда можно не добавлять дополнительный вызов функции и вложенность, которые появляются при использовании IIFE.
Мы можем проще выполнять минификацию
С модулями JavaScript нам больше не нужно отделять остальной код от IIFE, чтобы выполнить минификацию наших файлов должным образом.
Webpack, Browserify, Parcel, Rollup и так далее могут работать с модулями должным образом, поэтому мы должны использовать их для создания более чистого кода.
Вывод
Пора перестать использовать IIFE в нашем коде. Это добавляет лишние функции и избыточную вложенность.
Кроме того, сейчас это анахронизм: IIFE применялись еще до появления и широкого распространения практики использования модулей в JavaScript. В 2020 году для разделения кода мы должны использовать модули и автономные блоки.
Для предотвращения доступа извне к переменным, находящимся внутри модуля, мы можем использовать блочные области видимости.