Замыкания приносят много преимуществ ... но также и ряд ошибок. То же, что делает их мощными, делает их вполне способными создавать беспорядок, если вы не будете осторожны.
Помимо проблемы с циклическими ссылками (которая больше не является проблемой, поскольку IE6 практически не используется за пределами Китая), есть по крайней мере еще один огромный потенциальный минус: Они могут усложнить область действия. . При правильном использовании они улучшают модульность и совместимость, позволяя функциям обмениваться данными, не подвергая их воздействию ... но при неправильном использовании может быть трудно, если не невозможно, точно отследить, где установлена или изменена переменная.
JavaScript без замыканий имеет три * области видимости для переменных: уровень блока, уровень функции и глобальный. Там нет области уровня объекта. Без замыканий вы знаете, что переменная либо объявлена в текущей функции, либо в глобальном объекте (потому что именно там живут глобальные переменные).
С замыканиями у вас больше нет такой уверенности. Каждая вложенная функция представляет другой уровень области видимости, и любые замыкания, созданные внутри этой функции, видят ( в основном ) те же переменные, что и содержащая функция. Большая проблема состоит в том, что каждая функция может определять свои собственные переменные по желанию, которые скрывают внешние.
Правильное использование замыканий требует, чтобы вы (а) знали о том, как замыкания и var
влияют на область действия, и (б) отслеживают область, в которой находятся ваши переменные. В противном случае переменные могут случайно использоваться совместно (или псевдопеременные). потерян!), и могут возникнуть всевозможные дураки.
Рассмотрим этот пример:
function ScopeIssues(count) {
var funcs = [];
for (var i = 0; i < count; ++i) {
funcs[i] = function() { console.log(i); }
}
return funcs;
}
Коротко, просто ... и почти наверняка сломано. Смотреть:
x = ScopeIssues(100);
x[0](); // outputs 100
x[1](); // does too
x[2](); // same here
x[3](); // guess
Каждая функция в массиве выводит count
. Что тут происходит? Вы видите эффект объединения замыканий с неправильным пониманием замкнутых переменных и области видимости.
Когда создаются замыкания, они не используют значение i
во время их создания, чтобы определить, что выводить. Они используют переменную i
, которая используется совместно с внешней функцией и все еще изменяется. Когда они выводят его, они выводят значение в то время, когда оно называется . Это будет равно count
, значение, которое привело к остановке цикла.
Чтобы это исправить, вам понадобится еще одно закрытие.
function Corrected(count) {
var funcs = [];
for (var i = 0; i < count; ++i) {
(function(which) {
funcs[i] = function() { console.log(which); };
})(i);
}
}
x = Corrected(100);
x[0](); // outputs 0
x[1](); // outputs 1
x[2](); // outputs 2
x[3](); // outputs 3
Другой пример:
value = 'global variable';
function A() {
var value = 'local variable';
this.value = 'instance variable';
(function() { console.log(this.value); })();
}
a = new A(); // outputs 'global variable'
this
и arguments
различны; в отличие от всего остального, они не совместно используются через границы закрытия ? . Каждый вызов функции переопределяет их - и если вы не вызовете функцию, такую как
obj.func(...)
func.call(obj, ...)
func.apply(obj, [...])
или
var obj_func = func.bind(obj); obj_func(...)
для указания this
, тогда вы получите значение по умолчанию для this
: глобальный объект. ^
Самая распространенная идиома для решения проблемы this
- это объявление переменной и установка ее значения в this
. Наиболее распространенные имена, которые я видел, это that
и self
.
function A() {
var self = this;
this.value = 'some value';
(function() { console.log(self.value); })();
}
Но это делает self
реальной переменной со всей потенциальной странностью, которая влечет за собой. К счастью, редко требуется изменить значение self
без переопределения переменной ... но внутри вложенной функции переопределение self
, конечно, переопределяет его для всех функций, вложенных в него. И вы не можете сделать что-то вроде
function X() {
var self = this;
var Y = function() {
var outer = self;
var self = this;
};
}
из-за подъема . JavaScript эффективно перемещает все объявления переменных в начало функции. Это делает приведенный выше код эквивалентным
function X() {
var self, Y;
self = this;
Y = function() {
var outer, self;
outer = self;
self = this;
};
}
self
уже является локальной переменной до запуска outer = self
, поэтому outer
получает локальное значение, которое на данный момент равно undefined
. Вы только что потеряли свою ссылку на внешний self
.
* Начиная с ES7. Раньше их было всего два, а переменные было еще проще отследить. : P
?Функции, объявленные с использованием лямбда-синтаксиса (нового для ES7), не переопределяют this
и arguments
.Что потенциально усложняет ситуацию еще больше.
^ Более новые интерпретаторы поддерживают так называемый "строгий режим": функция подписки, которая нацелена на то, чтобы определенные шаблоны ненадежного кода либо полностью сработали, либонаносить меньше урона.В строгом режиме this
по умолчанию undefined
, а не глобальный объект.Но это все-таки нечто совершенно иное, чем вы обычно намеревались возиться.