Статьи

Область применения и замыкания

В JavaScript область действия — это контекст, в котором выполняется код. Существует три типа областей: глобальная область, локальная область (иногда называемая «областью действия») и область действия eval.

Код, определенный с использованием var внутри функции, имеет локальную область видимости и является «видимым» только для других выражений в этой функции, которая включает код внутри любых вложенных / дочерних функций. Переменные, определенные в глобальной области видимости, могут быть доступны из любого места, поскольку это самый высокий уровень и последняя остановка в цепочке областей действия.

Изучите следующий код и убедитесь, что вы понимаете, что каждое объявление foo уникально из-за области действия.

Образец: sample110.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html><html lang=»en»><body><script>
 
    var foo = 0;
    console.log(foo);
 
    var myFunction = function () {
 
        var foo = 1;
 
        console.log(foo);
 
        var myNestedFunction = function () {
 
            var foo = 2;
 
            console.log(foo);
        } ();
    } ();
 
    eval(‘var foo = 3; console.log(foo);’);
 
</script></body></html>

Убедитесь, что вы понимаете, что каждая переменная foo содержит свое значение, потому что каждая из них определена в специально определенной области.

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

Глобальная область является последней остановкой в ​​цепочке областей.

Функции, которые содержат функции, создают сложенные области выполнения. Эти стеки, которые связаны друг с другом, часто называют цепочкой областей действия.


Поскольку логические операторы ( if ) и циклические операторы ( for ) не создают области видимости, переменные могут перезаписывать друг друга. Изучите следующий код и убедитесь, что вы понимаете, что значение foo переопределяется, когда программа выполняет код.

Образец: sample111.html

01
02
03
04
05
06
07
08
09
10
11
12
13
<!DOCTYPE html><html lang=»en»><body><script>
 
    var foo = 1;
 
    if (true) {
        foo = 2;
        for (var i = 3; i <= 5; i++) {
            foo = i;
            console.log(foo);
        }
    }
 
</script></body></html>

Таким образом, foo меняется по мере выполнения кода, потому что JavaScript не имеет блока: только функция, глобальная или eval область.


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

Образец: sample112.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html><html lang=»en»><body><script>
 
    var foo = function () {
        var boo = function () {
            bar = 2;
        } ();
    } ();
 
    console.log(bar);
 
    // As opposed to…
 
    var foo = function () {
        var boo = function () {
            var doo = 2;
        } ();
    } ();
 
    // console.log(doo);
 
</script></body></html>

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


При поиске в JavaScript значения, связанного с переменной, следует цепочка поиска. Эта цепочка основана на иерархии области видимости. В следующем коде я sayHiText значение sayHiText из области func2 функции func2 .

Образец: sample113.html

01
02
03
04
05
06
07
08
09
10
11
<!DOCTYPE html><html lang=»en»><body><script>
 
    var sayHiText = ‘howdy’;
 
    var func1 = function () {
        var func2 = function () {
            console.log(sayHiText);
        } ();
    } ();
 
</script></body></html>

Как найти значение sayHiText если оно не содержится внутри области действия функции func2 ? Сначала JavaScript ищет в функции func2 переменную с именем sayHiText . Не найдя там func2 , он ищет родительскую функцию func1 , func1 . Переменная sayHiText не найдена в области действия func1 , поэтому JavaScript затем переходит в глобальную область, где находится sayHiText , и в этот момент sayHiText значение sayHiText . Если sayHiText не был определен в глобальной области видимости, undefined был бы возвращен JavaScript.

Это очень важная концепция для понимания. Давайте рассмотрим другой пример кода, в котором мы берем три значения из трех разных областей.

Образец: sample114.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!DOCTYPE html><html lang=»en»><body><script>
 
    var x = 10;
    var foo = function () {
        var y = 20;
        var bar = function () {
            var z = 30;
            console.log(z + y + x);
        } ();
    } ()
 
    foo();
 
</script></body></html>

Значение для z является локальным по отношению к функции bar и контексту, в котором вызывается console.log . Значение для y находится в функции foo , которая является родителем bar() , а значение для x находится в глобальной области видимости. Все они доступны для функции bar через цепочку областей действия. Убедитесь, что вы понимаете, что ссылки на переменные в функции bar будут проверять всю цепочку областей действия для указанных переменных.

Цепочка прицелов, если подумать, ничем не отличается от цепочки прототипов. И то, и другое — просто способ поиска значения путем проверки систематического и иерархического набора местоположений.


В следующем примере кода переменная с именем x существует в той же области, в которой она проверяется с помощью console.log . Это «локальное» значение x используется, и можно сказать, что оно скрывает или маскирует переменные с одинаковыми именами, найденные далее в цепочке областей действия.

Образец: sample115.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!DOCTYPE html><html lang=»en»><body><script>
 
    var x = false;
    var foo = function () {
        var x = false;
        bar = function () {
            var x = true;
            console.log(x);
        } ();
    }
 
    foo();
 
</script></body></html>

Помните, что поиск области заканчивается, когда переменная найдена в ближайшем доступном звене цепочки, даже если то же имя переменной используется далее в цепочке.


Поскольку функции определяют область видимости, а функции можно передавать так же, как любое значение JavaScript, можно подумать, что расшифровка цепочки областей видимости сложна. Это на самом деле очень просто. Цепочка областей действия определяется на основе расположения функции во время определения, а не во время вызова. Это также называется лексическим ограничением. Задумайтесь об этом долго и усердно, так как большинство людей часто сталкиваются с этим в коде JavaScript.

Цепочка контекста создается перед вызовом функции. Из-за этого мы можем создавать замыкания. Например, у нас может быть функция, возвращающая вложенную функцию в глобальную область видимости, но наша функция может по-прежнему получать доступ через область видимости к области видимости своей родительской функции. В следующем примере мы определяем parentFunction которая возвращает анонимную функцию, и вызываем возвращенную функцию из глобальной области видимости. Поскольку наша анонимная функция была определена как содержащаяся в parentFunction , она все еще имеет доступ к области parentFunctions при вызове. Это называется закрытием.

Образец: sample116.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<!DOCTYPE html><html lang=»en»><body><script>
 
    var parentFunction = function () {
        var foo = ‘foo’;
        return function () { // Anonymous function being returned.
            console.log(foo);
        }
    }
 
    // nestedFunction refers to the nested function returned from parentFunction.
    var nestedFunction = parentFunction();
 
    nestedFunction();
 
</script></body></html>

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


Возьмите то, что вы узнали о цепочке областей видимости и области видимости из этой статьи, и замыкание не должно быть слишком сложным для понимания. В следующем примере мы создаем функцию countUpFromZero . Эта функция фактически возвращает ссылку на дочернюю функцию, содержащуюся в ней. Когда эта дочерняя функция (вложенная функция) вызывается, она все еще имеет доступ к области действия родительской функции из-за цепочки областей действия.

Образец: sample117.html

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<!DOCTYPE html><html lang=»en»><body><script>
 
    var countUpFromZero = function () {
        var count = 0;
        return function () { // Return nested child function when countUpFromZero is invoked.
            return ++count;
        };
    } ();
 
    console.log(countUpFromZero());
    console.log(countUpFromZero());
    console.log(countUpFromZero());
 
</script></body></html>

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


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