Лексическое окружение функций в JavaScript

Давайте рассмотрим следующий код:

let num = 1; // задаем значение переменной function func() { alert(num); // выводим его на экран } func(); // вызываем функцию

Как я уже упоминал ранее, значение переменной не обязательно должно быть перед определением функции, главное, чтобы оно стояло перед ее вызовом:

function func() { alert(num); } let num = 1; func();

На самом деле это не совсем так. Наша функция даже до своего вызова знает значение переменной num:

let num = 1; function func() { alert(num); // функция уже знает, что num = 1 }

Вот более сложный пример:

let num = 1; // функция в этот момент узнает, что num = 1 function func() { alert(num); } num = 2; // функция в этот момент узнает, что num = 2

Добавим вызовы функции:

let num = 1; // функция в этот момент узнает, что num = 1 func(); // выведет 1 function func() { alert(num); } func(); // выведет 1 num = 2; // функция в этот момент узнает, что num = 2 func(); // выведет 2

Еще раз: на самом деле функция знает значения внешних переменных, даже не будучи вызванной.

Лексическое окружение

Все внешние, доступные функции переменные, называются ее лексическим окружением (англ. LexicalEnvironment).

В следующем примере функции доступны две переменные: num1 и num2, которые и являются лексическим окружением нашей функции:

let num1 = 1; let num2 = 2; function func() { // функция знает про переменные num1 и num2 }

Само лексическое окружение представляет собой некий внутренний объект JavaScript, привязанный к нашей функции. В данном случае его можно представить в следующем виде:

{num1: 1, num2: 2}

Значение любой переменной лексического окружения всегда равно текущему значению этой переменной:

let num1 = 1; // окружение {num1: 1} let num2 = 2; // окружение {num1: 1, num2: 2} // Поменяем переменную num1: num1 = 123; // окружение {num1: 123, num2: 2} function func() { }

Когда мы пытаемся обратится к какой-либо переменной внутри функции, эта переменная вначале ищется среди локальных переменных функции и, если такой переменной там нет, то ищется в лексическом окружении функции.

Практическое применение

Пусть у нас есть функция, своим результатом возвращающая другую функцию:

function test() { return function() { } }

Если родительская функция имеет какие-либо переменные, то эти переменные будут содержаться в лексическом окружении возвращаемой функции:

function test() { let num = 1; // переменная родительской функции return function() { // лексическое окружение = {num: 1} } }

Напишем в коде нашей возвращаемой функции алерт, выводящий на экран значение переменной num:

function test() { let num = 1; return function() { alert(num); } }

Давайте теперь вызовем родительскую функцию test и результат ее работы запишем в переменную func:

function test() { let num = 1; return function() { alert(num); } } let func = test();

В переменную func запишется возвращаемая функция. Давайте вызовем нашу функцию - своим результатом она выведет содержимое переменной num:

function test() { let num = 1; return function() { alert(num); } } let func = test(); func(); // выведет 1

Если же просто попытаться вывести переменную num вне функции - она будет недоступна:

function test() { let num = 1; return function() { alert(num); } } alert(num); // переменная num тут недоступна

Как вы видите, локальная переменная num привязалась к лексическому окружению нашей функции и теперь, вызвав в любом месте кода эту функцию, мы сможем получить значение переменной num, даже если в месте вызова сама по себе эта переменная и недоступна.

На самом деле аналогичного результата можно добиться, сделав переменную num глобальной:

function test() { return function() { alert(num); } } let num = 1; // глобальная переменная let func = test(); func(); // выведет 1

Здесь, однако, будет существенная разница: в новом варианте переменную num можно менять вне функций (так как она глобальная), а в старом - нет.

Определите, не запуская код, что выведется на экран:

function test() { let num1 = 1; let num2 = 2; return function() { return num1 + num2; } } let func = test(); alert(func());

Определите, не запуская код, что выведется на экран:

function test() { let num1 = 1; let num2 = 2; return function() { return num1 + num2; } } alert(test()());

Определите, не запуская код, что выведется на экран:

function test() { let num1 = 1; return function() { return num1 + num2; } } let num2 = 2; let func = test(); alert(func());

Определите, не запуская код, что выведется на экран:

function test() { let num = 1; return function() { return num; } } let num = 2; let func = test(); alert(func());