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