Урок 5. Основы JavaScript. Область видимости и замыкания
Область видимости – фундаментальное понятие в JavaScript. С ним необходимо познакомиться и разобраться, чтобы правильно строить работу. Помимо этого у JS есть специальные возможности, которые упрощают процесс разработки. Одна из них – замыкание. Давайте по порядку.
Область видимости
Областью видимости в JavaScript называют часть программы, в пределах которой компьютер видит набор всех созданных переменных, функций и других данных, и может к ним обратиться.
Базово разделить область видимости можно на глобальную и локальную. Глобальная – это область видимости сразу всей программы. А локальная – область видимости каждой отдельной части кода в программе, например, внутри функции.
Пример
Представим, что наша программа состоит из двух переменных и одной функции, которую мы разбирали в Уроке 4:
let someVar = 20;
let someOtherVar = 30;
function sum (a, b) {
let result = a + b;
return result;
}
В глобальную область видимости войдут переменные someVar и someOtherVar, а также функция sum.
В локальной видимости находится то, что указано внутри функции sum: два параметра a и b, а также переменная result.
Добавим ещё одну функцию:
function someFn (a, b, c ,d) {
let var1 = 10;
let var2 = a+ b + c + d + var1;
return var2
}
Теперь в глобальную область видимости добавилась и функция someFn, а в функции someFn образовалась собственная локальная область видимости, которая состоит из параметров a, b, c, d и двух переменных var1 и var2.
Замыкания
Замыкания – важная особенность языка JavaScript. В чём она заключается? Каждая функция знает, где и когда её объявили. Это позволяет ей пользоваться всеми данными, которые существовали в области видимости на момент объявления функции.
Пример
Снова возьмём наш код, который состоит из двух переменных и одной функции, но попросим функцию выполнить сложение не только параметров a и b, но и значений из переменных someVar и someOtherVar. Это выглядит так:
let someVar = 20;
let someOtherVar = 30;
function sum (a, b) {
let result = a + b + someVar + someOtherVar;
return result;
}
Запишем результат функции sum с аргументами 2 и 3 в переменную result, чтобы затем вывести её значение в консоль:
let result = sum(2,3);
Справка! Сейчас в нашем коде две переменные result. Первая – в локальной области видимости внутри функции. А вторую мы создали в глобальной области видимости. Так как они созданы в разных зонах видимости, то не конфликтуют друг с другом. Если в глобальной области мы попробуем создать еще одну переменную result, то увидим ошибку «Идентификатор 'result' уже объявлен»:
Uncaught SyntaxError: Identifier 'result' has already been declared
Выведем результат в консоль:
console.log(result);
Так как функция sum объявлена в глобальной области видимости после переменных someVar и someOtherVar, то она может ими пользоваться. В консоли увидим результат:
55
Если бы мы сначала создали функцию sum, а затем переменные someVar и someOtherVar, то функция бы не сработала.
Здесь замыкание – это способность функции sum обратиться к созданным ранее переменным someVar и someOtherVar.
Как интерпретатор пришёл к результату 55? Рассмотрим пошагово:
1. Мы попросили его выполнить заданную функцию с аргументами 2 и 3 и вывести результат:
let result = sum(2,3);
console.log(result);
2. Он проводит поиск, берёт заданные аргументы и подставляет в соответствующие параметры их значения.
3. Сначала поиск происходит в локальной области видимости – внутри функции. В этой области он видит параметры a и b, равные 2 и 3 соответственно. Подставляет:
function sum (a, b) {
let result = 2 + 3 + someVar + someOtherVar;
return result;
}
Теперь ему нужно найти значения переменных someVar и someOtherVar. Как он это сделает? Закончив поиск внутри локальной области и не найдя там нужных значений, интерпретатор переходит в более широкую область. В данном случае – это глобальная. Здесь находится сама функция sum и значения переменных someVar и someOtherVar. Интерпретатор подставляет их в функцию вот так:
function sum (a, b) {
let result = 2 + 3 + 20 + 30;
return result;
}
Поэтому, выводя в консоль результаты функции, мы и получили значение 55.
Усложним задачу и сделаем несколько локальных областей видимости.
Для этого создадим переменную a и функцию fn1, в которую вложена ещё одна функция fn2:
let a = 10;
function fn1(b, c) {
function fn2(d, e) {
return a + b + c + d + e;
}
return fn2 (3,4);
}
Теперь попросим компьютер выдать нам результат функции fn1 с аргументами 1 и 2, записав их в переменную result:
let result = fn1(1,2);
console.log(result);
Как интерпретатор вычисляет результат в этом случае?
Рассмотрим подробнее выражение, записанное в функции fn2 – a + b + c + d + e:
Сначала ищем значение a. В первую очередь поиск происходит внутри локальной области видимости первого уровня – это область видимости функции fn2:
function fn2(d, e) {
return a + b + c + d + e;
}
Здесь значения a нет.
Поиск переходит в локальную область видимости более высокого уровня, в которой создана функция fn2 – это функция fn1:
function fn1(b, c) {
function fn2(d, e) {
return a + b + c + d + e;
}
return fn2 (3,4);
}
Но и здесь значения a нет.
Тогда поиск переходит на следующий уровень видимости, куда вложена функция fn1 – это глобальная область. И здесь находит переменную a со значением 10. Подставляет его в выражение: 10 + b + c + d + e.
Продолжает поиск параметров b и c сначала в своей области видимости. Не находит. Затем обнаруживает их в локальной области видимости функции fn1, которым мы передали значение 1 и 2. Подставляет: 10 + 1 + 2 + d + e.
Продолжает поиск параметров d и e и сразу находит их в своей локальной области видимости, подставляет переданные значения 3 и 4: 10 + 1 + 2 + 3 + 4.
Так мы видим в консоли итоговый результат:
55
Здесь замыкание – это способность функции fn2 вызвать переменную a и параметры функции fn1, так как на момент создания fn2 эти данные уже существовали.
Теперь вы знаете, что такое область видимости и замыкания.