В MDN формальное определение IIFE или выражения немедленного вызова функции - это функция JavaScript, которая запускается, как только она определена. Пример IIFE:
(function() { console.log('Hello there!') })();
Вот очень простой IIFE, который консоль регистрирует Hello there!
. Как только JavaScript встречает этот код, код в IIFE вызывается ()
и запускается один раз. Поскольку функциональные выражения выводят значение, мы можем сохранить значение IIFE в переменной:
let greeting = (function() { return 'Hello there!'; })(); console.log(greeting);
Этот код будет записывать Hello there!
в консоль, поскольку это возвращаемое значение IIFE, которое было сохранено в переменной greeting
.
Это подводит нас к тому, почему IIFE полезны!
IIFE могут создавать частную область и частные данные
В идеальном мире без запутанного кода и без тысяч строк кода мы можем легко объявить что-то вроде этого:
// lots of code above function greeting() { console.log('Hello there!'); } greeting(); //lots of code below
Это довольно простой фрагмент кода, но проблема здесь в том, что мы создали глобальную переменную с именем greeting
и понятия не имеем, была ли она уже объявлена в приведенном выше коде и что мы непреднамеренно переопределяем ее. На помощь приходит IIFE. Поскольку IIFE заключен в пару ()
, синтаксический анализатор JavaScript знает, что это выражение функции, а не объявление функции, что позволяет нам немедленно вызвать его с парой ()
. А поскольку это выражение функции, это анонимная функция, и глобальная переменная не объявляется! Ура!
// lots of code above (function() { console.log('Hello there!'); })(); //lots of code below
Что делает этот IIFE, так это то, что он по существу создает частную область в пределах, возможно, тысяч строк кода, и беспрепятственно выполняет функциональность, не опасаясь столкновения с любыми ранее существовавшими переменными.
Еще одна функция IIFE - это способность создавать личные данные.
Рассмотрим приведенный ниже пример кода:
let count = 0; function keepCount() { count++; return count; }
У нас есть простая keepCount
функция, которая увеличивает переменную count
при каждом вызове. Однако главный недостаток здесь в том, что кто-то может непреднамеренно переназначить значение count
. Решение здесь - использовать IIFE с закрытием!
let keepCount = (function() { let count = 0; return function() { count++; return count; }; })(); keepCount();
Здесь мы возвращаем функцию, которая имеет доступ к моим личным данным, count
. Используя IIFE с замыканием, мы сделали count
приватную переменную, доступ к которой можно получить только внутри функции, а не за ее пределами. А поскольку IIFE вызывается только один раз, а последующие вызовы относятся к внутренней функции, нам не нужно беспокоиться о том, что count
будет сброшен в 0.
Другой вариант использования этой комбинации IIFE и закрытия - использование IIFE для возврата объекта с доступом к личным данным. Это полезно, когда мы хотим получить объект, содержащий информацию, которую мы не хотим раскрывать. Допустим, у нас есть коллекция машин:
let carCollection = { collection: [], listCollection() { this.collection.forEach(car => { console.log(car.model + ', ' + car.year); }); }, }; carCollection.collection.push({ model: 'Corvette', year: 1957, }); carCollection.listCollection();
Этот carCollection
объект отлично подходит для хранения информации о вашей изысканной коллекции автомобилей, но он не так хорош для сокрытия информации от конечного пользователя или защиты от несчастных случаев на случай, если мы случайно сотрем всю коллекцию и сбросим carCollection.collection
на []
или что-нибудь еще. С помощью IIFE мы можем немного его настроить.
let carCollection = (function() { let collection = []; return { listCollection() { collection.forEach(car => { console.log(car.model + ', ' + car.year) }); }, addCar(carObj) { collection.push(carObj); } }; })();
Теперь мы успешно сделали collection
частную переменную, и она больше не доступна за пределами IIFE. И поэтому, когда мы пытаемся получить прямой доступ к переменной collection
следующим образом:
carCollection.collection.push({ model: 'Corvette', year: 1957, })
Код выводит TypeError: Cannot read property ‘push’ of undefined
, потому что collection
не является свойством в возвращаемом объекте. Итак, чтобы добавить collection
, нам нужно сделать следующее:
carCollection.addCar({ model: 'Corvette', year: 1957, });
И с использованием замыкания данные сохраняются, и наш массив collection
имеет доступ к предыдущим добавленным элементам. И снова, поскольку это IIFE, выражение функции выполняется только один раз, что создает нашу переменную collection
, а в возвращаемом объекте нет метода для сброса или переназначения нашей переменной collection
. Уф, мы в безопасности! И вот мы получаем это прекрасное творение:
let carCollection = (function() { let collection = []; return { listCollection() { collection.forEach(car => { console.log(car.model + ', ' + car.year) }); }, addCar(carObj) { collection.push(carObj); } }; })(); carCollection.addCar({ model: 'Corvette', year: 1957, }); carCollection.addCar({ model: 'Duesenberg SJ LA Phaeton', year: 1935, }); carCollection.listCollection(); //Corvette, 1957 //Duesenberg SJ LA Phaeton, 1935
Рассмотрим следующий пример кода:
let myPets = { numOfPets: 0, adoptAPet() { this.numOfPets++; return this.numOfPets; }, listPetNum() { console.log('The number of pets I have currently: ' + this.numOfPets); }, }; myPets.listPetNum(); // returns 0 myPets.adoptAPet(); myPets.listPetNum(); // returns 1 myPets.numOfPets = 10; myPets.listPetNum(); // returns 10
Здесь у меня есть объект myPets
, у которого есть два метода: один для добавления питомца, а другой для отображения количества домашних животных. Проблема здесь в том, что когда я устанавливаю для своего свойства numOfPets
какое-то другое значение, намеренно или непреднамеренно, я в конечном итоге изменяю фактическое количество моих питомцев. Итак, я хочу, чтобы общее количество домашних животных было закрытым, чтобы переназначение моего numOfPets
случайно не изменило мое общее количество домашних животных. IIFE может это исправить!
let myPets = (function() { let numOfPets = 0; return { adoptAPet() { numOfPets++; return numOfPets; }, listPetNum() { console.log('The number of pets I have currently: ' + numOfPets); }, }; })(); myPets.listPetNum(); // returns 0 myPets.adoptAPet(); myPets.listPetNum(); // returns 1 myPets.numOfPets = 10; // creates a numOfPets property and sets it to 10 myPets.listPetNum(); // returns 1
Используя IIFE, мы сделали numOfPets
частную переменную. В одной из строк у нас есть myPets.numOfPets = 10
, который создает свойство с именем numOfPets
в возвращаемом объекте IIFE. Поскольку мой метод listPetNum
обращается к частной переменной numOfPets
, а не к свойству numOfPets
, вызов метода вернет правильное частное значение numOfPets
.