var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
// and store them in funcs
funcs[i] = function() {
// each should log its value.
console.log("My value: " + i);
};
}
for (var j = 0; j < 3; j++) {
// and now let's run each one to see
funcs[j]();
}
Это выводит это:
Моя ценность: 3
Моя ценность: 3
Моя ценность: 3
Принимая во внимание, что я хотел бы это вывести:
Моя ценность: 0
Моя ценность: 1
Моя ценность: 2
Та же проблема возникает, когда задержка запуска функции вызвана использованием прослушивателей событий:
var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
// as event listeners
buttons[i].addEventListener("click", function() {
// each should log its value.
console.log("My value: " + i);
});
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>
... или асинхронный код, например, используя Promises:
// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));
for (var i = 0; i < 3; i++) {
// Log `i` as soon as each promise resolves.
wait(i * 100).then(() => console.log(i));
}
Редактировать: Это также очевидно for in
и в for of
петлях:
const arr = [1,2,3];
const fns = [];
for(i in arr){
fns.push(() => console.log(`index: ${i}`));
}
for(v of arr){
fns.push(() => console.log(`value: ${v}`));
}
for(f of fns){
f();
}
Каково решение этой основной проблемы?
javascript
loops
closures
nickf
источник
источник
funcs
быть массивом, если используете числовые индексы? Просто один на один.Ответы:
Проблема в том, что переменная
i
в каждой из ваших анонимных функций привязана к одной и той же переменной за пределами функции.Классическое решение: затворы
Что вы хотите сделать, это связать переменную в каждой функции с отдельным неизменным значением вне функции:
Поскольку в JavaScript отсутствует область блока - только область функции - оборачивая создание функции в новую функцию, вы гарантируете, что значение «i» останется таким, как вы предполагали.
Решение ES5.1: forEach
Учитывая относительно широкую доступность
Array.prototype.forEach
функции (в 2015 году), стоит отметить, что в тех ситуациях, когда итерация выполняется в основном по массиву значений, она.forEach()
обеспечивает чистый, естественный способ получения четкого замыкания для каждой итерации. То есть, если у вас есть какой-то массив, содержащий значения (ссылки DOM, объекты и т. Д.), И возникает проблема настройки обратных вызовов, специфичных для каждого элемента, вы можете сделать это:Идея состоит в том, что каждый вызов функции обратного вызова, используемой с
.forEach
циклом, будет его собственным закрытием. Параметр, передаваемый этому обработчику, является элементом массива, специфичным для данного конкретного шага итерации. Если он используется в асинхронном обратном вызове, он не будет конфликтовать ни с одним из других обратных вызовов, установленных на других этапах итерации.Если вы работаете в jQuery,
$.each()
функция предоставляет вам аналогичные возможности.Решение ES6:
let
ECMAScript 6 (ES6) представляет новые
let
иconst
ключевые слова, которые имеют область действия, отличную отvar
переменных на основе. Например, в цикле сlet
индексом на основе, каждая итерация цикла будет иметь новую переменнуюi
с областью действия цикла, поэтому ваш код будет работать так, как вы ожидаете. Ресурсов много, но я бы рекомендовал пост-обзор 2ality в качестве отличного источника информации.Остерегайтесь, однако, того, что IE9-IE11 и Edge до Edge 14 поддерживают,
let
но ошибаетесь в вышеприведенном (они не создают новыйi
каждый раз, поэтому все функции выше будут регистрировать 3, как если бы мы использовалиvar
). Край 14, наконец, понимает это правильно.источник
function createfunc(i) { return function() { console.log("My value: " + i); }; }
все еще не закрытие, потому что оно использует переменнуюi
?Function.bind()
определенно предпочтительнее, см. Stackoverflow.com/a/19323214/785541 ..bind()
это «правильный ответ» это не так. У каждого из них свое место. При этом.bind()
вы не можете связывать аргументы без привязкиthis
значения. Также вы получаете копиюi
аргумента без возможности изменять его между вызовами, что иногда необходимо. Так что это совершенно разные конструкции, не говоря уже о том, что.bind()
реализации были исторически медленными. Конечно, в простом примере будет работать, но замыкания являются важной концепцией для понимания, и это то, о чем был вопрос.Пытаться:
Изменить (2014):
Лично я думаю, что более свежий ответ
.bind
@ Aust об использовании - лучший способ сделать это сейчас. Там же вот-тир / Подчеркивание это ,_.partial
когда вам не нужно или хотите возиться сbind
-хthisArg
.источник
}(i));
?i
в качестве аргументаindex
функции.Еще один способ, который еще не был упомянут, это использование
Function.prototype.bind
ОБНОВИТЬ
Как указали @squint и @mekdev, вы получаете лучшую производительность, создавая функцию вне цикла, а затем связывая результаты внутри цикла.
источник
_.partial
.bind()
будет в значительной степени устареть с функциями ECMAScript 6. Кроме того, это фактически создает две функции на итерацию. Сначала аноним, потом созданный.bind()
. Лучше было бы создать его вне цикла, затем.bind()
внутри.bind
это используется. Я добавил еще один пример в соответствии с вашими предложениями.this
.Использование выражения немедленного вызова функции , самый простой и удобный для чтения способ заключить индексную переменную:
Это отправляет итератор
i
в анонимную функцию, которую мы определяем какindex
. Это создает замыкание, где переменнаяi
сохраняется для последующего использования в любой асинхронной функциональности в IIFE.источник
i
чему, я бы переименовал параметр функции вindex
.index
вместоi
.var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
.forEach()
, но большую часть времени, когда каждый начинает с массива,forEach()
это хороший выбор, например:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
Немного опоздал на вечеринку, но я изучал этот вопрос сегодня и заметил, что многие ответы не полностью касаются того, как Javascript обрабатывает области видимости, и это, по сути, сводится к
Таким образом, как уже упоминалось, проблема в том, что внутренняя функция ссылается на ту же
i
переменную. Так почему бы нам просто не создавать новую локальную переменную на каждой итерации и вместо этого ссылаться на внутреннюю функцию?Как и раньше, когда каждая внутренняя функция выводила последнее назначенное значение
i
, теперь каждая внутренняя функция просто выводит последнее назначенное значениеilocal
. Но не должна ли каждая итерация иметь свою собственнуюilocal
?Оказывается, это проблема. Каждая итерация использует одну и ту же область видимости, поэтому каждая итерация после первой просто перезаписывается
ilocal
. От MDN :Подтверждено для акцента:
Мы можем убедиться в этом, проверив
ilocal
перед тем, как объявить это в каждой итерации:Именно поэтому эта ошибка такая хитрая. Даже если вы переделываете переменную, Javascript не выдаст ошибку, а JSLint даже не выдаст предупреждение. Вот почему лучший способ решить эту проблему - воспользоваться преимуществами замыканий, что, по сути, означает, что в Javascript внутренние функции имеют доступ к внешним переменным, поскольку внутренние области «заключают» внешние области.
Это также означает, что внутренние функции «держат» внешние переменные и поддерживают их, даже если внешняя функция возвращается. Чтобы использовать это, мы создаем и вызываем функцию-оболочку исключительно для создания новой области, объявляем
ilocal
в новой области и возвращаем внутреннюю функцию, которая используетilocal
(более подробное объяснение ниже):Создание внутренней функции внутри функции-оболочки дает внутренней функции частную среду, к которой только она может получить доступ, «замыкание». Таким образом, каждый раз, когда мы вызываем функцию-обертку, мы создаем новую внутреннюю функцию со своей отдельной средой, гарантируя, что
ilocal
переменные не конфликтуют и не перезаписывают друг друга. Несколько небольших оптимизаций дают окончательный ответ, который дали многие другие пользователи SO:Обновить
Теперь ES6 стал массовым, и теперь мы можем использовать новое
let
ключевое слово для создания блочных переменных:Посмотрите, как это просто сейчас! Для получения дополнительной информации см. Этот ответ , на котором основана моя информация.
источник
let
иconst
ключевые слова. Если бы этот ответ был расширен, чтобы включить его, на мой взгляд, он был бы гораздо более полезным для всего мира.let
и связал более полное объяснениеi=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }
? (может заменить window.open () с getelementbyid ......)i
в своих функциях тайм-аута, поэтому вам не нужно закрытиеБлагодаря широкому распространению ES6 лучший ответ на этот вопрос изменился. ES6 предоставляет
let
иconst
ключевые слова для этого точного обстоятельства. Вместо того, чтобы возиться с замыканиями, мы можем простоlet
установить переменную области видимости цикла следующим образом:val
затем будет указывать на объект, который специфичен для этого конкретного поворота цикла, и будет возвращать правильное значение без дополнительной записи замыкания. Это, очевидно, значительно упрощает эту проблему.const
аналогичноlet
дополнительному ограничению, что имя переменной не может быть привязано к новой ссылке после первоначального присваивания.Поддержка браузеров теперь доступна для тех, кто ориентирован на последние версии браузеров.
const
/let
в настоящее время поддерживаются в последних версиях Firefox, Safari, Edge и Chrome. Он также поддерживается в Node, и вы можете использовать его где угодно, используя такие инструменты сборки, как Babel. Вы можете увидеть рабочий пример здесь: http://jsfiddle.net/ben336/rbU4t/2/Документы здесь:
Остерегайтесь, однако, того, что IE9-IE11 и Edge до Edge 14 поддерживают,
let
но ошибаетесь в вышеприведенном (они не создают новыйi
каждый раз, поэтому все функции выше будут регистрировать 3, как если бы мы использовалиvar
). Край 14, наконец, понимает это правильно.источник
Еще один способ сказать, что
i
ваша функция связана во время выполнения функции, а не во время ее создания.Когда вы создаете закрытие,
i
это ссылка на переменную, определенную во внешней области видимости, а не ее копия, как это было при создании замыкания. Он будет оценен во время исполнения.Большинство других ответов предоставляют способы обхода путем создания другой переменной, которая не изменит значения для вас.
Просто подумал, что добавлю объяснение для ясности. Для решения лично я бы пошел с Харто, так как это самый очевидный способ сделать это из ответов здесь. Любой из опубликованного кода будет работать, но я бы выбрал фабрику замыканий, а не писать кучу комментариев, чтобы объяснить, почему я объявляю новую переменную (Фредди и 1800-е) или у меня странный встроенный синтаксис замыкания (apphacker).
источник
Что вам нужно понять, так это то, что область действия переменных в javascript основана на функции. Это важное отличие, чем, скажем, c #, где у вас есть область видимости блока, и просто скопировать переменную в одну из for будет работать.
Завершение этого в функцию, которая оценивает возвращение функции, как ответ apphacker, сделает свое дело, поскольку переменная теперь имеет область действия функции.
Существует также ключевое слово let вместо var, которое позволяет использовать правило области видимости блока. В этом случае определение переменной внутри for сделает свое дело. Тем не менее, ключевое слово let не является практическим решением из-за совместимости.
источник
Вот еще один вариант метода, похожий на метод Бьорна (apphacker), который позволяет назначать значение переменной внутри функции, а не передавать ее в качестве параметра, что иногда может быть более понятным:
Обратите внимание, что какой бы метод вы ни использовали, эта
index
переменная становится своего рода статической переменной, связанной с возвращаемой копией внутренней функции. Т.е. изменения его значения сохраняются между вызовами. Это может быть очень удобно.источник
var
линию иreturn
линия не будет работать? Спасибо!var
иreturn
тогда переменная не будет назначен , прежде чем он вернулся внутреннюю функцию.Это описывает распространенную ошибку с использованием замыканий в JavaScript.
Функция определяет новую среду
Рассматривать:
Каждый раз, когда
makeCounter
вызывается,{counter: 0}
приводит к созданию нового объекта. Также создается новая копияobj
для ссылки на новый объект. Таким образом,counter1
иcounter2
не зависят друг от друга.Замыкания в петлях
Использовать замыкание в цикле сложно.
Рассматривать:
Обратите внимание, что
counters[0]
и неcounters[1]
являются независимыми. На самом деле они работают на том жеobj
!Это потому, что существует только одна копия
obj
общего доступа на всех итерациях цикла, возможно, по соображениям производительности. Хотя{counter: 0}
в каждой итерации создается новый объект, одна и та же копияobj
будет обновляться со ссылкой на самый новый объект.Решение состоит в том, чтобы использовать другую вспомогательную функцию:
Это работает, потому что локальные переменные в области действия функции непосредственно, а также переменные аргумента функции, выделяются новые копии при входе.
источник
var obj = {counter: 0};
выполняется оценка перед выполнением любого кода, как указано в: MDN var : var объявления, где бы они ни происходили, обрабатываются перед выполнением любого кода.Самое простое решение было бы,
Вместо использования:
который предупреждает "2", 3 раза. Это связано с тем, что анонимные функции, созданные в цикле for, имеют одно и то же замыкание, и в этом замыкании значение
i
одинаково. Используйте это для предотвращения общего закрытия:Идея, лежащая в основе этого, заключается в том, чтобы инкапсулировать все тело цикла for с помощью IIFE -выражения (немедленного вызова функции) и передать его
new_i
в качестве параметра и записать его какi
. Поскольку анонимная функция выполняется немедленно,i
значение отличается для каждой функции, определенной внутри анонимной функции.Это решение, похоже, подходит для любой такой проблемы, поскольку оно потребует минимальных изменений исходного кода, страдающего от этой проблемы. На самом деле, это по замыслу, это не должно быть проблемой вообще!
источник
попробуйте этот более короткий
нет массива
нет лишних для цикла
http://jsfiddle.net/7P6EN/
источник
Вот простое решение, которое использует
forEach
(работает обратно в IE9):Печать:
источник
Основная проблема с кодом, показанным OP, заключается в том, что
i
он никогда не читается до второго цикла. Чтобы продемонстрировать, представьте, что видите ошибку внутри кодаОшибка на самом деле не происходит, пока
funcs[someIndex]
не выполнится()
. Используя ту же логику, должно быть очевидно, что значениеi
также не собирается до этой точки. Как только исходный цикл заканчивается,i++
приводитi
к значению,3
которое приводит кi < 3
сбою условия и завершению цикла. На данный момент,i
это3
и так, когдаfuncs[someIndex]()
используется иi
оценивается, это 3 - каждый раз.Чтобы обойти это, вы должны оценить,
i
как он встречается. Обратите внимание, что это уже произошло в формеfuncs[i]
(где есть 3 уникальных индекса). Есть несколько способов получить это значение. Одним из них является передача его в качестве параметра функции, которая показана здесь несколькими способами.Другой вариант - создать объект функции, который сможет закрывать переменную. Это может быть достигнуто таким образом
jsFiddle Demo
источник
Функции JavaScript «закрывают» область, к которой они имеют доступ при объявлении, и сохраняют доступ к этой области, даже если переменные в этой области изменяются.
Каждая функция в массиве выше закрывается над глобальной областью действия (глобальной, просто потому, что это та область, в которой они объявлены).
Позже эти функции вызываются, регистрируя самое последнее значение
i
в глобальной области видимости. Это магия и разочарование закрытия.«Функции JavaScript закрывают область, в которой они объявлены, и сохраняют доступ к этой области даже при изменении значений переменных внутри этой области».
Использование
let
вместо решенияvar
этой проблемы путем создания новой области видимости при каждом запускеfor
цикла, создавая отдельную область видимости для каждой закрываемой функции. Различные другие методы делают то же самое с дополнительными функциями.(
let
Делает область переменных переменной. Блоки обозначаются фигурными скобками, но в случае цикла for переменная инициализацииi
в нашем случае считается объявленной в фигурных скобках.)источник
i
устанавливается в глобальном масштабе. Когдаfor
цикл завершает работу, глобальное значениеi
становится равным 3. Следовательно, всякий раз, когда эта функция вызывается в массиве (используя, скажем,funcs[j]
),i
функция в этой функции ссылается на глобальнуюi
переменную (которая равна 3).Прочитав различные решения, я хотел бы добавить, что причина, по которой эти решения работают, заключается в том, чтобы опираться на концепцию цепочки областей действия . Это способ, которым JavaScript разрешает переменную во время выполнения.
var
и своихarguments
.window
.В исходном коде:
Когда
funcs
будет выполнено, цепочка областей будетfunction inner -> global
. Поскольку переменнаяi
не может быть найдена вfunction inner
(не объявлена с использованием иvar
не передана в качестве аргументов), она продолжает поиск, пока значение вi
конечном итоге не будет найдено в глобальной области видимостиwindow.i
.Обернув его во внешнюю функцию, либо явно определите вспомогательную функцию, как это сделал harto , либо используйте анонимную функцию, как это сделал Бьорн :
Когда
funcs
будет выполнено, теперь цепочка областей будетfunction inner -> function outer
. Это времяi
можно найти в области видимости внешней функции, которая выполняется 3 раза в цикле for, каждый раз значение имеетi
правильную привязку. Он не будет использовать значение,window.i
когда выполняется внутри.Более подробную информацию можно найти здесь.
Она включает в себя распространенную ошибку при создании замыкания в цикле, такую как то, что у нас здесь, а также зачем нам нужно замыкание и анализ производительности.
источник
Array.prototype.forEach(function callback(el) {})
связаны друг с другом, становится яснее понять, почему другие «современные» способы, такие как естественные, работают: обратный вызов, который передается естественным образом, формирует область обтекания, в которой el правильно связывается в каждой итерацииforEach
. Таким образом, каждая внутренняя функция, определенная вel
С новыми функциями ES6 можно управлять областями на уровне блоков:
Код в вопросе OP заменен на
let
вместоvar
.источник
const
обеспечивает тот же результат, и его следует использовать, когда значение переменной не изменится. Однако использованиеconst
внутри инициализатора цикла for неправильно реализовано в Firefox и еще не исправлено. Вместо того, чтобы быть объявленным внутри блока, он объявляется вне блока, что приводит к повторному объявлению переменной, что, в свою очередь, приводит к ошибке. Использованиеlet
внутри инициализатора правильно реализовано в Firefox, поэтому не нужно беспокоиться там.Я удивлен, что никто еще не предложил использовать
forEach
функцию, чтобы лучше избегать (пере) использования локальных переменных. На самом деле я больше не пользуюсьfor(var i ...)
по этой причине.// отредактировано для использования
forEach
вместо карты.источник
.forEach()
это гораздо лучший вариант, если вы на самом деле ничего не делаете, и Дэрил предложил сделать это за 7 месяцев до публикации, так что нечего удивляться.Этот вопрос действительно показывает историю JavaScript! Теперь мы можем избежать выделения блоков с помощью функций стрелок и обрабатывать циклы непосредственно из узлов DOM, используя методы Object.
источник
Причина, по которой исходный пример не сработал, заключается в том, что все замыкания, созданные в цикле, ссылаются на один и тот же кадр. По сути, наличие 3 методов на одном объекте только с одной
i
переменной. Все они распечатаны одинакового значения.источник
Прежде всего, поймите, что не так с этим кодом:
Здесь, когда
funcs[]
массив инициализируется,i
увеличивается,funcs
массив инициализируется и размерfunc
массива становится равным 3, поэтомуi = 3,
. Теперь, когдаfuncs[j]()
вызывается, он снова использует переменнуюi
, которая уже была увеличена до 3.Теперь, чтобы решить это, у нас есть много вариантов. Ниже приведены два из них:
Мы можем инициализировать
i
с помощьюlet
или инициализировать новую переменнуюindex
с помощьюlet
и сделать ее равнойi
. Поэтому, когда будет сделан вызов,index
будет использоваться и его область действия закончится после инициализации. И для звонка,index
будет инициализирован снова:Другим вариантом может быть введение,
tempFunc
которое возвращает фактическую функцию:источник
Используйте структуру замыкания , это уменьшит ваш дополнительный цикл for. Вы можете сделать это в одном цикле for:
источник
Case1 : использование
var
Теперь откройте окно консоли Chrome , нажав клавишу F12, и обновите страницу. Расширьте каждые 3 функции внутри массива. Вы увидите свойство
[[Scopes]]
.Expand, что. Вы увидите один названный объект массива"Global"
, разверните его. Вы найдете'i'
объявленное в объекте свойство, имеющее значение 3.Вывод:
'var'
функцию, она становится глобальной переменной (вы можете проверить это, набравi
илиwindow.i
в окне консоли. Она вернет 3).console.log("My value: " + i)
берет значение из ееGlobal
объекта и отображает результат.CASE2: используя let
Теперь замените
'var'
с'let'
Сделай то же самое, иди в прицелы. Теперь вы увидите два объекта
"Block"
и"Global"
. Теперь развернитеBlock
объект, вы увидите, что здесь определено «i», и странно то, что для каждой функции значение ifi
различно (0, 1, 2).Вывод:
Когда вы объявляете переменную, используя
'let'
даже вне функции, но внутри цикла, эта переменная не будет глобальной переменной, она станетBlock
переменной уровня, которая доступна только для одной и той же функции. По этой причине мы получаем значениеi
различных для каждой функции, когда мы вызываем функции.Для получения более подробной информации о том, как ближе работает, пожалуйста, просмотрите удивительный видеоурок https://youtu.be/71AtaJpJHw0
источник
Вы можете использовать декларативный модуль для списков данных, таких как query-js (*). В этих ситуациях я лично считаю декларативный подход менее удивительным
Затем вы можете использовать свой второй цикл и получить ожидаемый результат, или вы могли бы сделать
(*) Я являюсь автором query-js и поэтому склоняюсь к его использованию, поэтому не принимайте мои слова в качестве рекомендации для указанной библиотеки только для декларативного подхода :)
источник
Query.range(0,3)
? Это не часть тегов для этого вопроса. Кроме того, если вы используете стороннюю библиотеку, вы можете предоставить ссылку на документацию.Я предпочитаю использовать
forEach
функцию, которая имеет свое собственное закрытие с созданием псевдодальности:Это выглядит ужаснее, чем диапазоны в других языках, но ИМХО менее чудовищно, чем другие решения.
источник
И еще одно решение: вместо создания другого цикла просто привяжите
this
функцию возврата.Связывая это , решает проблему также.
источник
Многие решения кажутся правильными, но они не упоминают, что это называется
Currying
функциональным шаблоном программирования для подобных ситуаций. В 3-10 раз быстрее, чем привязка в зависимости от браузера.Смотрите прирост производительности в разных браузерах .
источник
Ваш код не работает, потому что он делает:
Теперь вопрос в том, каково значение переменной
i
при вызове функции? Поскольку первый цикл создается с условиемi < 3
, он немедленно останавливается, когда условие ложно, так оно и естьi = 3
.Вы должны понимать, что во время создания ваших функций ни один из их кодов не выполняется, он сохраняется только для последующего использования. И поэтому, когда они вызываются позже, интерпретатор выполняет их и спрашивает: «Какова текущая стоимость
i
?»Итак, ваша цель - сначала сохранить значение
i
для функции, и только после этого сохранить функцию вfuncs
. Это можно сделать, например, так:Таким образом, каждая функция будет иметь свою собственную переменную,
x
и мы устанавливаем этоx
значениеi
в каждой итерации.Это только один из множества способов решения этой проблемы.
источник
источник
Используйте let (block-scope) вместо var.
источник