В 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.