В JavaScript функции могут содержать внутри другие функции — это называется вложенностью. Подобно тому, как циклы и условные конструкции могут быть вложенными, функции также могут определяться внутри других функций.
Рассмотрим базовый пример:
function doOuterFunctionStuff(nr) {
console.log("Outer function");
doInnerFunctionStuff(nr);
function doInnerFunctionStuff(x) {
console.log(x + 7);
console.log("I can access outer variables:", nr);
}
}
doOuterFunctionStuff(2);При выполнении этого кода мы получим:
Outer function
9
I can access outer variables: 2
Вложенная функция определяется внутри внешней функции и может быть вызвана только из пределах этой внешней функции (за исключением некоторых особых случаев).
Самое интересное в этом механизме — то, что внутренняя функция имеет доступ к переменным и параметрам внешней функции. В примере выше внутренняя функция doInnerFunctionStuff имеет доступ к параметру nr внешней функции, хотя он не был явно передан внутрь.
Область видимости — ключевое понятие при работе с вложенными функциями. Переменные, объявленные внутри функции, доступны только внутри этой функции и во всех вложенных функциях.
Рассмотрим пример, демонстрирующий ограничения области видимости:
function doOuterFunctionStuff(nr) {
doInnerFunctionStuff(nr);
function doInnerFunctionStuff(x) {
let z = 10; // z объявлена внутри внутренней функции
}
console.log("Not accessible:", z); // Ошибка!
}
doOuterFunctionStuff(2);В этом примере мы попытаемся обратиться к переменной z вне функции, где она была объявлена. Это вызовет ошибку ReferenceError, поскольку переменная z существует только внутри функции doInnerFunctionStuff.
Вложенные функции не доступны извне родительской функции. Попытка вызвать вложенную функцию снаружи приведет к ошибке:
function doOuterFunctionStuff(nr) {
doInnerFunctionStuff(nr);
function doInnerFunctionStuff(x) {
let z = 10;
}
}
// Эта строка вызовет ошибку:
doInnerFunctionStuff(3); // ReferenceErrorМы получаем ошибку ReferenceError, потому что doInnerFunctionStuff определена только внутри области видимости функции doOuterFunctionStuff и не существует вне ее.
Вложенные функции полезны в нескольких сценариях:
- Инкапсуляция логики: Когда нужно скрыть вспомогательную логику внутри основной функции
- Замыкания: Для создания функций, которые "запоминают" окружение, в котором были созданы
- Рекурсивные вспомогательные функции: Когда нужно реализовать рекурсию с дополнительными параметрами
Пример с инкапсуляцией:
function processUserData(userData) {
validateData(userData);
process();
function validateData(data) {
// Логика проверки данных
if (!data.name) throw new Error("Name is required");
}
function process() {
// Основная логика обработки
console.log("Processing:", userData.name);
}
}Одно из самых мощных применений вложенных функций — создание замыканий. Замыкание — это функция, которая запоминает внешние переменные и имеет к ним доступ даже после того, как внешняя функция завершила выполнение.
Пример замыкания:
function createCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3В этом примере внутренняя функция "запоминает" переменную count и может изменять ее при каждом вызове, даже though функция createCounter уже завершила выполнение.
-
Попытка вызвать вложенную функцию снаружи: Всегда помните, что вложенная функция доступна только внутри родительской функции.
-
Непонимание области видимости: Переменные, объявленные внутри вложенной функции, недоступны снаружи.
-
Циклы и вложенные функции: Будьте осторожны при создании функций внутри циклов, так как они могут захватывать изменяемые переменные.
Пример проблемы с циклом:
// Неожиданное поведение:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // Всегда выводит 3
}, 100);
}
// Решение:
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // Выводит 0, 1, 2
}, 100);
})(i);
}-
Создайте функцию
calculate, которая принимает два числа и операцию (как строку), а внутри содержит вложенные функции для сложения, вычитания, умножения и деления. -
Напишите функцию
createGreeter, которая принимает приветствие (например, "Hello") и возвращает функцию, которая будет принимать имя и выводить полное приветствие. -
Реализуйте функцию-счётчик, используя замыкания, которая может увеличивать, уменьшать значение и выводить его текущее состояние.