Этот пост в блоге объясняет, как переменные находятся в 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