Замыкания и ссылки¶
Одним из самых мощных инструментов JavaScript'а считаются возможность создавать замыкания — это такой приём, когда наша область видимости всегда имеет доступ к внешней области, в которой она была объявлена. Собственно, единственный механизм работы с областями видимости в JavaScript — это функции: т. о. объявляя функцию, вы автоматически реализуете замыкания.
Эмуляция приватных свойств¶
function Counter(start) {
var count = start
return {
increment: function () {
count++
},
get: function () {
return count
},
}
}
var foo = Counter(4)
foo.increment()
foo.get() // 5
В данном примере Counter
возвращает два замыкания: функции increment
и get
. Обе эти функции сохраняют ссылку на область видимости Counter
и, соответственно, имеют доступ к переменной count
из этой самой области.
Как это работает¶
Поскольку в JavaScript нельзя присваивать или ссылаться на области видимости, заполучить count
извне не представляется возможным. Единственным способом взаимодействовать с ним остается использование двух замыканий.
var foo = new Counter(4)
foo.hack = function () {
count = 1337
}
В приведенном примере мы не изменяем переменную count
в области видимости Counter
, т.к. foo.hack
не объявлен в данной области. Вместо этого будет создана или перезаписана глобальная переменная count
;
Замыкания внутри циклов¶
Часто встречается ошибка, когда замыкания используют внутри циклов, передавая переменную индекса внутрь.
for (var i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i)
}, 1000)
}
Данный код не будет выводить числа с 0
до 9
, вместо этого число 10
будет выведено десять раз.
Анонимная функция сохраняет ссылку на i
и, когда будет вызвана функция console.log
, цикл for
уже закончит свою работу, а в i
будет содержаться 10
.
Для получения желаемого результата необходимо создать копию переменной i
.
Во избежание ошибок¶
Для того, чтобы скопировать значение индекса из цикла, лучше всего использовать анонимную функцию как обёртку.
for (var i = 0; i < 10; i++) {
;(function (e) {
setTimeout(function () {
console.log(e)
}, 1000)
})(i)
}
Анонимная функция-обертка будет вызвана сразу же, и в качестве первого аргумента получит i
, значение которой будет скопировано в параметр e
.
Анонимная функция, которая передается в setTimeout
, теперь содержит ссылку на e
, значение которой не изменяется циклом.
Еще одним способом реализации является возврат функции из анонимной функции-обертки, поведение этого кода будет таким же, как и в коде из предыдущего примера.
for (var i = 0; i < 10; i++) {
setTimeout(
(function (e) {
return function () {
console.log(e)
}
})(i),
1000
)
}
Замечание от перев. Переменную
e
можно тоже назватьi
, если вы хотите: это не поменяет поведения кода — внутренняя переменнаяi
всё так же будет копией внешней переменной