Статьи

Область видимости переменной JavaScript и ее подводные камни

Этот пост в блоге объясняет, как переменные находятся в JavaScript. Он указывает на две вещи, которые могут вызвать проблемы, если вы о них не знаете: переменные JavaScript имеют функциональную область и могут быть захвачены в замыканиях.

Область действия переменной определяет, где переменная доступна. Например, если переменная объявлена ​​в начале функции, она доступна изнутри этой функции, но не извне и обычно умирает, когда функция завершается. В этом случае функция является областью действия переменной. Когда вводится область, создается новая среда, которая отображает имена переменных в значения. Области могут быть вложенными. Переменная доступна в своей области и во всех областях, вложенных в эту область.

Подводный камень 1: Переменные имеют функциональную область. Большинство основных языков имеют блочную область видимости — при вводе блока создаются новые среды, а области вложенных блоков. Напротив, переменные JavaScript являются областями функций — новые среды создаются только при вводе функции, а области вложены вложенными функциями. Это означает, что даже если вы объявляете переменную внутри блока, такого как блок «then» оператора if, она доступна везде в окружающей функции. Следующий код иллюстрирует это.

    var myvar = "global";
    function f() {
        print(myvar); // (*)
        if (true) {
            var myvar = "local"; // (**)
        }
        print(myvar);
    }
    > f()
    undefined
    local
    > myvar
    global

Как видите, даже первый доступ к myvar относится к его локальному значению (которое еще не назначено в (*)). Причина этого заключается в том, что вар объявленных переменные
водрузила в JavaScript: вар MyVar = «локальный» эквивалентно объявление MyVar в начале е () и выполнение простого задания на месте (**). Таким образом, в JavaScript рекомендуется использовать var только в начале функции.

Ловушка 2: Затворы. Область видимости в JavaScript является статической , она определяется вложенностью синтаксических конструкций. [В дополнение, JavaScript с оператором поддерживает динамическое определение области действия. Но это утверждение, вероятно, будет удалено из языка в долгосрочной перспективе.] Чтобы обеспечить статическую область видимости, среда привязана к значениям, которые обращаются к переменным в среде. Примером такого значения является возвращаемая функция в следующем коде.

    function f() {
        var x = "abc";
        return function() {
            return x;
        }
    }

Взаимодействие:

    
    > var g = f();
    > g()
    abc

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

    function objMaker(color) {
        return {
            getColor: function() {
                return color;
            },
            setColor: function(c) {
                color = c;
            }
        };
    }

Взаимодействие:

    > var c = objMaker("blue");
    > c.getColor()
    blue
    > c.setColor("green");
    > c.getColor()       
    green

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

    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var func = function() {
                return arr[i];
            };
            result.push(func);
        }
        return result;
    }

Эта функция возвращает массив с двумя функциями. Обе эти функции могут по-прежнему иметь доступ к среде f и, таким образом, обр. На самом деле, они живут в одной среде. Но в этой среде у меня есть значение 2, и поэтому обе функции возвращают «синий» при вызове:

    > f()[0]()
    blue

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

    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var j=i; // fresh copy for func? Not in JavaScript!
            var func = function() {
                return arr[j];
            };
            result.push(func);
        }
        return result;
    }

JavaScript имеет функциональную область, поэтому j обрабатывается так, как если бы он был объявлен в начале функции f (), и мы не получаем разную среду для каждого возвращаемого нами функционала. Для этого нам нужно использовать функцию.

    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var func = function() {
                var j=i; // fresh copy
                return function() {
                    return arr[j];
                }
            }();
            result.push(func);
        }
        return result;
    }

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

    > f()[0]()
    red

Определение функции и ее немедленный вызов — это обычная модель в JavaScript, поскольку она позволяет создавать новые среды. Вы также можете сделать цвет параметром функции создания среды.

    function f() {
        var arr = [ "red", "green", "blue" ];
        var result = [];
        for(var i=0; i<arr.length-1; i++) {
            var func = function(color) {
                return function() {
                    return color;
                }
            }(arr[i]);
            result.push(func);
        }
        return result;
    }

Обратите внимание, что пример действительно имеет отношение к реальной ситуации, поскольку аналогичные сценарии возникают при добавлении обработчиков к элементам DOM.

 

С http://www.2ality.com/2011/02/javascript-variable-scoping-and-its.html