Спасибо всем за вашу помощь. Я взял много ваших идей и собрал их вместе, дайте мне знать, что вы думаете.
Я добавил два метода в класс следующим образом:
def hours(self):
retval = ""
if self.totalTime:
hoursfloat = self.totalTime.seconds / 3600
retval = round(hoursfloat)
return retval
def minutes(self):
retval = ""
if self.totalTime:
minutesfloat = self.totalTime.seconds / 60
hoursAsMinutes = self.hours() * 60
retval = round(minutesfloat - hoursAsMinutes)
return retval
В моем джанго я использовал это (сумма - это объект, и он находится в словаре):
<td>{{ sum.0 }}</td>
<td>{{ sum.1.hours|stringformat:"d" }}:{{ sum.1.minutes|stringformat:"#02.0d" }}</td>
Ну, проблема в том, что переменная i
в каждой из ваших анонимных функций связана с одной и той же переменной вне функции.
Что вы хотите сделать, так это привязать переменную в каждой функции к отдельному неизменному значению вне функции:
var funcs = [];
function createfunc(i) {
return function() { console.log("My value: " + i); };
}
for (var i = 0; i < 3; i++) {
funcs[i] = createfunc(i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Поскольку там не является областью блока в JavaScript - только в области функций - путем обертывания создания функции в новой функции, вы гарантируете, что значение «i» останется таким, каким вы намеревались.
. При относительно широкой доступности функции Array.prototype.forEach
(в 2015 году) стоит отметить, что в тех ситуациях, в которых задействована итерация в основном по массиву значений, .forEach()
обеспечивает чистый, естественный способ получить отличное закрытие для каждой итерации. То есть, предполагая, что у вас есть какой-то массив, содержащий значения (DOM-ссылки, объекты и т. Д.), И возникает проблема настройки обратных вызовов, специфичных для каждого элемента, вы можете сделать это:
var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
// ... code code code for this one element
someAsynchronousFunction(arrayElement, function() {
arrayElement.doSomething();
});
});
Идея состоит в том, что каждый вызов функции обратного вызова, используемой в цикле .forEach
, будет его собственным закрытием. Параметр, переданный этому обработчику, является элементом массива, специфичным для этого конкретного этапа итерации. Если он используется в асинхронном обратном вызове, он не столкнется с каким-либо другим обратным вызовом, установленным на других этапах итерации.
Если вы работаете в jQuery, функция $.each()
дает вам аналогичная возможность.
let
ECMAScript 6 (ES6), новейшая версия JavaScript, теперь начинает реализовываться во многих вечнозеленых браузерах и бэкэнд-системы. Существуют также транспиляторы, такие как Babel , которые преобразуют ES6 в ES5, чтобы использовать новые функции в старых системах.
ES6 вводит новые let
и const
ключевые слова, иначе, чем переменные, основанные на var
. Например, в цикле с индексом, основанным на let
, каждая итерация через цикл будет иметь новое значение i
, где каждое значение ограничено внутри цикла, поэтому ваш код будет работать так, как вы ожидаете. Есть много ресурсов, но я бы рекомендовал двухступенчатый столбец как отличный источник информации.
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
Остерегайтесь, однако, что IE9-IE11 и Edge ранее к поддержке Edge 14 let
, но получить вышеизложенное (они не создают новый i
каждый раз, поэтому все вышеперечисленные функции будут записывать 3, как если бы мы использовали var
). Edge 14, наконец, получает это право.
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(param) { // and store them in funcs
console.log("My value: " + param); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](j); // and now let's run each one to see with j
}
Когда ES6 теперь широко поддерживается, лучший ответ на этот вопрос изменился. ES6 предоставляет ключевые слова let
и const
для этого точного обстоятельства. Вместо того, чтобы возиться с закрытием, мы можем просто использовать let
для установки переменной области цикла таким образом:
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function() {
console.log("My value: " + i);
};
}
val
затем укажет на объект, специфичный для этого конкретного поворота цикл, и вернет правильное значение без дополнительной записи закрытия. Это явно значительно упрощает эту проблему.
const
аналогично let
с дополнительным ограничением на то, что имя переменной не может отскочить к новой ссылке после первоначального присваивания.
Поддержка браузера теперь предназначена для тех, кто ориентирован на последние версии браузеров. const
/ let
в настоящее время поддерживается в последних версиях Firefox, Safari, Edge и Chrome. Он также поддерживается в узле, и вы можете использовать его в любом месте, используя инструменты построения, такие как Babel. Вы можете увидеть рабочий пример здесь: http://jsfiddle.net/ben336/rbU4t/2/
Документы здесь:
Остерегайтесь, однако, что поддержка IE9-IE11 и Edge до Edge 14 let
, но получим неверное (они не создают новый i
каждый раз, поэтому все вышеперечисленные функции будут записывать 3, как если бы мы использовали var
). Edge 14, наконец, получает это право.
Мы проверим, что на самом деле происходит, когда вы объявляете
var
иlet
один за другим.Case1: используя
var
<script> var funcs = []; for (var i = 0; i < 3; i++) { funcs[i] = function () { debugger; console.log("My value: " + i); }; } console.log(funcs); </script>
Теперь откройте окно консоли Chrome, нажав F12 и обновите страницу. Расходуйте каждые 3 функции внутри массива. Вы увидите свойство, называемое
[[Scopes]]
. Разместите это. Вы увидите один объект массива с именем"Global"
, разверните его. Вы найдете свойство'i'
, объявленное в объект, имеющий значение 3.Вывод:
- Когда вы объявляете переменную с помощью
'var'
вне функции, она становится глобальной переменной (вы можете проверить, введяi
илиwindow.i
в окне консоли. return 3).- Объявленная вами анонимная функция не вызовет и не проверит значение внутри функции, если вы не вызываете функции.
- Когда вы вызываете функцию,
console.log("My value: " + i)
принимает значение из его объектаGlobal
и отобразить результат.CASE2: использование let
Теперь замените
'var'
на'let'
]<script> var funcs = []; for (let i = 0; i < 3; i++) { funcs[i] = function () { debugger; console.log("My value: " + i); }; } console.log(funcs); </script>
Сделайте то же самое, перейдите в области. Теперь вы увидите два объекта
"Block"
и"Global"
. Теперь разворачиваем объектBlock
, вы увидите там 'i', и странно, что для каждой функции значение ifi
отличается (0, 1, 2).Заключение:
Когда вы объявляете переменную, используя
'let'
даже вне функции, но внутри цикла, эта переменная будет не будет глобальной переменной, она станет переменной уровняBlock
, которая доступна только для одной и той же функции. Именно поэтому мы получаем значениеi
для каждой функции при вызове функций.Для получения более подробной информации о том, как работает ближе, пройдите через удивительный видеоурок https://youtu.be/71AtaJpJHw0
Вот еще одна вариация в технике, подобная Bjorn's (apphacker), которая позволяет вам назначать значение переменной внутри функции, а не передавать ее как параметр, который иногда может быть более ясным:
for (var i = 0; i < 3; i++) {
funcs[i] = (function() {
var index = i;
return function() {
console.log("My value: " + index);
}
})();
}
Обратите внимание, что любой метод, который вы используете, переменная index
становится своего рода статической переменной, связанной с возвращенной копией внутренней функции. I.e., изменения его значения сохраняются между вызовами. Это может быть очень удобно.
var
и return
не сработает? Благодаря!
– midnite
3 December 2013 в 04:56
var
и return
, тогда переменная не будет назначена до того, как вернет внутреннюю функцию.
– Boann
3 December 2013 в 06:35
После прочтения различных решений я хотел бы добавить, что причина, по которой эти решения работают, заключается в том, чтобы полагаться на концепцию цепочки областей видимости. Это способ, которым JavaScript разрешает переменную во время выполнения.
var
и ее arguments
. window
. В начальном code:
funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function inner() { // function inner's scope contains nothing
console.log("My value: " + i);
};
}
console.log(window.i) // test value 'i', print 3
Когда выполнение funcs
выполняется, цепочка областей видимости будет function inner -> global
. Поскольку переменную i
невозможно найти в function inner
(ни объявлено с использованием var
, ни передано как аргументы), она продолжает поиск, пока значение i
не будет найдено в глобальной области видимости window.i
.
Обернув его во внешнюю функцию, либо явно определите вспомогательную функцию, как harto , либо использовали анонимную функцию, например Bjorn :
funcs = {};
function outer(i) { // function outer's scope contains 'i'
return function inner() { // function inner, closure created
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = outer(i);
}
console.log(window.i) // print 3 still
Когда выполняется funcs
, теперь цепочка видимости будет function inner -> function outer
. На этот раз i
можно найти в области внешней функции, которая выполняется 3 раза в цикле for, каждый раз имеет значение i
правильно. Он не будет использовать значение window.i
, когда внутреннее исполнение выполнено.
Более подробно можно найти здесь . В него входит общая ошибка при создании замыкания в цикле, как то, что мы имеем здесь, а также почему мы нуждаемся в закрытии и рассмотрении эффективности.
Array.prototype.forEach(function callback(el) {})
: Обратный вызов, который передается, естественно формирует область обтекания с правильной привязкой к каждой итерации forEach
. Поэтому каждая внутренняя функция, определенная в обратном вызове, сможет использовать правое значение el
– wpding
26 April 2017 в 14:19
i
в качестве аргумента index
функции.
– Jet Blue
26 July 2018 в 22:01
Функции JavaScript «закрывают» область, к которой они имеют доступ при объявлении, и сохраняют доступ к этой области, даже если переменные в этой области меняются.
var funcs = []
for (var i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
Каждая функция в массиве выше закрывается по глобальной области (глобальная, просто потому, что это область видимости, в которой они объявлены).
Позже эти функции вызывают наибольшую регистрацию текущее значение i
в глобальной области. Это волшебство и разочарование закрытия.
«Функции JavaScript закрываются по области, в которой они объявлены, и сохраняют доступ к этой области, даже если переменные значения внутри этой области изменяются».
Использование let
вместо var
решает это, создавая новую область каждый раз, когда цикл for
запускается, создавая выделенную область для каждой функции для закрытия. Различные другие методы делают то же самое с дополнительными функциями.
var funcs = []
for (let i = 0; i < 3; i += 1) {
funcs[i] = function () {
console.log(i)
}
}
for (var k = 0; k < 3; k += 1) {
funcs[k]()
}
(let
делает переменные, которые ограничены областью, а не областью действия функции. Блоки обозначаются фигурными фигурными скобками, но в случае цикла for переменная инициализации i
в нашем случае считается объявленной в фигурных скобках.)
i
устанавливается в глобальную область. Когда цикл for
завершается, глобальное значение i
теперь равно 3. Следовательно, всякий раз, когда эта функция вызывается в массиве (используя, скажем, funcs[j]
), i
в этой функции ссылается на глобальную i
(что равно 3).
– Modermo
5 April 2017 в 02:50
Вот простое решение, которое использует forEach
(возвращается к IE9):
var funcs = {};
[0,1,2].forEach(function(i) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
})
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Печатает:
blockquote>My value: 0 My value: 1 My value: 2
Что вам нужно понять, так это объем переменных в javascript, основанный на функции. Это важная разница, чем, скажем, c #, где у вас есть область блока, и просто копирование переменной в единицу внутри будет работать.
Обертывание ее в функции, которая оценивает возвращение функции, как ответ афкакера, сделает трюк, так как переменная теперь имеет область видимости функции.
Существует также ключевое слово let вместо var, что позволит использовать правило области блока. В этом случае определение переменной внутри for сделает трюк. Тем не менее, ключевое слово let не является практическим решением из-за совместимости.
var funcs = {};
for (var i = 0; i < 3; i++) {
let index = i; //add this
funcs[i] = function() {
console.log("My value: " + index); //change to the copy
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Еще один способ, который еще не упоминался, - использование Function.prototype.bind
var funcs = {};
for (var i = 0; i < 3; i++) {
funcs[i] = function(x) {
console.log('My value: ' + x);
}.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
UPDATE
Как указано @squint и @mekdev, вы получаете лучшую производительность, сначала создавая функцию за пределами цикла, а затем привязывая результаты в цикле.
function log(x) {
console.log('My value: ' + x);
}
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = log.bind(this, i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
_.partial
– Bjorn Tipling
8 December 2014 в 06:18
.bind()
будет в значительной степени устаревать с функциями ECMAScript 6. Кроме того, это фактически создает две функции для каждой итерации. Сначала анонимный, затем тот, который генерируется .bind()
. Лучше использовать его для создания вне цикла, затем .bind()
внутри.
– user
28 June 2015 в 03:29
bind
. Я добавил еще один пример в ваших предложениях.
– Aust
29 June 2015 в 16:23
Самое простое решение:
Вместо использования:
var funcs = [];
for(var i =0; i<3; i++){
funcs[i] = function(){
alert(i);
}
}
for(var j =0; j<3; j++){
funcs[j]();
}
, который предупреждает «2», в 3 раза. Это связано с тем, что анонимные функции, созданные в цикле for, имеют одно и то же закрытие, а в этом закрытии значение i
одинаково. Используйте это, чтобы предотвратить совместное закрытие:
var funcs = [];
for(var new_i =0; new_i<3; new_i++){
(function(i){
funcs[i] = function(){
alert(i);
}
})(new_i);
}
for(var j =0; j<3; j++){
funcs[j]();
}
Идея этого заключается в том, что инкапсуляция всего тела цикла for с помощью IIFE (выражение с выражением немедленного вызова) и передача new_i
в качестве параметра и зафиксировать его как i
. Поскольку анонимная функция выполняется немедленно, значение i
отличается для каждой функции, определенной внутри анонимной функции.
Это решение похоже на любую проблему, так как оно потребует минимальных изменений в исходном коде, страдающем из этого вопроса. Фактически, это по дизайну, это не должно быть проблемой вообще!
Это описывает распространенную ошибку с использованием замыканий в JavaScript.
. Рассмотрим:
function makeCounter()
{
var obj = {counter: 0};
return {
inc: function(){obj.counter ++;},
get: function(){return obj.counter;}
};
}
counter1 = makeCounter();
counter2 = makeCounter();
counter1.inc();
alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0
Для каждого времени makeCounter
, {counter: 0}
приводит к созданию нового объекта. Кроме того, создается новая копия obj
для ссылки на новый объект. Таким образом, counter1
и counter2
не зависят друг от друга.
Использование замыкания в цикле является сложным.
Рассмотрим:
var counters = [];
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
}
makeCounters(2);
counters[0].inc();
alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1
Обратите внимание, что counters[0]
и counters[1]
независимы не . Фактически, они работают на одном и том же obj
!
Это связано с тем, что есть только одна копия obj
, разделенная на все итерации цикла, возможно, по соображениям производительности. Несмотря на то, что {counter: 0}
создает новый объект на каждой итерации, одна и та же копия obj
будет просто обновляться с ссылкой на самый новый объект.
Решение состоит в использовании другой вспомогательной функции:
function makeHelper(obj)
{
return {
inc: function(){obj.counter++;},
get: function(){return obj.counter;}
};
}
function makeCounters(num)
{
for (var i = 0; i < num; i++)
{
var obj = {counter: 0};
counters[i] = makeHelper(obj);
}
}
Это работает, потому что локальные переменные в области функции напрямую, а также переменные аргументов функции выделяются новыми экземплярами при записи.
Подробное обсуждение см. в JavaScript ловушки закрытия и использование
COUNTER PRIMITIVE
Давайте определим функции обратного вызова следующим образом:
// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
for (var i=0; i<2; i++) {
setTimeout(function() {
console.log(i);
});
}
}
test1();
// 2
// 2
По завершении таймаута он будет печатать 2 для обоих. Это связано с тем, что функция обратного вызова обращается к значению, основанному на лексической области , где была определена функция.
Чтобы передать и сохранить значение при определении обратного вызова, мы можем создать замыкание , чтобы сохранить значение до вызова обратного вызова. Это можно сделать следующим образом:
function test2() {
function sendRequest(i) {
setTimeout(function() {
console.log(i);
});
}
for (var i = 0; i < 2; i++) {
sendRequest(i);
}
}
test2();
// 1
// 2
Теперь в этом особенность: «Примитивы передаются по значению и копируются. Таким образом, когда ограничение определено, они сохраняют значение из предыдущего цикла».
COUNTER BEING OBJECT
Поскольку у закрытий есть доступ к родительским переменным функции через ссылку, этот подход будет отличаться от такового для примитивов.
// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
var index = { i: 0 };
for (index.i=0; index.i<2; index.i++) {
setTimeout(function() {
console.log('test3: ' + index.i);
});
}
}
test3();
// 2
// 2
Итак, даже если для переменной, передаваемой как объект, создается замыкание, значение индекса цикла не будет сохранено. Это означает, что значения объекта не копируются, а к ним обращаются через ссылку.
function test4() {
var index = { i: 0 };
function sendRequest(index, i) {
setTimeout(function() {
console.log('index: ' + index);
console.log('i: ' + i);
console.log(index[i]);
});
}
for (index.i=0; index.i<2; index.i++) {
sendRequest(index, index.i);
}
}
test4();
// index: { i: 2}
// 0
// undefined
// index: { i: 2}
// 1
// undefined
.forEach()
- гораздо лучший вариант, если вы на самом деле ничего не картировали, и Дэрил предположил, что за 7 месяцев до того, как вы отправили сообщение, так что нечего удивляться.
– JLRishe
31 March 2015 в 19:59
const
дает тот же результат и должен использоваться, когда значение переменной не изменится. Однако использование const
внутри инициализатора цикла for неверно реализовано в Firefox и еще не исправлено. Вместо того, чтобы быть объявленным внутри блока, он объявляется вне блока, что приводит к повторному описанию переменной, что, в свою очередь, приводит к ошибке. Использование let
внутри инициализатора правильно реализовано в Firefox, поэтому не нужно беспокоиться об этом.
– Tiny Giant
27 December 2017 в 04:05
Использование выражения Immediately-Вызываемой функции - самый простой и удобный способ заключить индексную переменную:
for (var i = 0; i < 3; i++) {
(function(index) {
console.log('iterator: ' + index);
//now you can also loop an ajax call here
//without losing track of the iterator value: $.ajax({});
})(i);
}
Это отправляет итератору i
в анонимный функцию которого мы определяем как index
. Это создает закрытие, где переменная i
сохраняется для последующего использования в любых асинхронных функциях внутри IIFE.
i
является тем, я бы переименовал параметр функции в index
.
– Kyle Falconer
10 January 2014 в 18:45
i
вы будете использовать index
.
– JLRishe
31 March 2015 в 20:54
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](); }
– Nico
1 April 2015 в 09:22
.forEach()
это не будет большим случаем, но много времени, когда вы начинаете с массива, forEach()
является хороший выбор, например: var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
– JLRishe
1 April 2015 в 10:05
И еще одно решение: вместо создания другого цикла просто привяжите функцию this
к функции возврата.
var funcs = [];
function createFunc(i) {
return function() {
console.log('My value: ' + i); //log value of i.
}.call(this);
}
for (var i = 1; i <= 5; i++) { //5 functions
funcs[i] = createFunc(i); // call createFunc() i=5 times
}
By связывая это, решает также проблему.
Я предпочитаю использовать функцию forEach
, которая имеет собственное закрытие с созданием псевдодиапазона:
var funcs = [];
new Array(3).fill(0).forEach(function (_, i) { // creating a range
funcs[i] = function() {
// now i is safely incapsulated
console.log("My value: " + i);
};
});
for (var j = 0; j < 3; j++) {
funcs[j](); // 0, 1, 2
}
Это выглядит более уродливым, чем диапазоны на других языках, но IMHO менее чудовищным, чем другие решения.
Вы можете использовать декларативный модуль для списков данных, таких как query-js (*). В этих ситуациях я лично считаю декларативный подход менее неожиданным
var funcs = Query.range(0,3).each(function(i){
return function() {
console.log("My value: " + i);
};
});
. Затем вы можете использовать свой второй цикл и получить ожидаемый результат, или вы могли бы сделать
funcs.iterate(function(f){ f(); });
(*) Я автор запросов-js и поэтому склонен к его использованию, поэтому не принимайте мои слова в качестве рекомендации для указанной библиотеки только для декларативного подхода:)
Query.range(0,3)
? Это не является частью тегов для этого вопроса. Кроме того, если вы используете стороннюю библиотеку, вы можете предоставить ссылку на документацию.
– jherax
27 October 2015 в 05:07
Этот вопрос действительно показывает историю JavaScript! Теперь мы можем избежать блочного сканирования с помощью функций стрелок и обрабатывать петли непосредственно из узлов DOM с использованием методов Object.
const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())
const buttons = document.getElementsByTagName("button");
Object
.keys(buttons)
.map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>
Основная проблема с кодом, показанным OP, заключается в том, что i
никогда не читается до второго цикла. Чтобы продемонстрировать, представьте, что вы видите ошибку внутри кода
funcs[i] = function() { // and store them in funcs
throw new Error("test");
console.log("My value: " + i); // each should log its value.
};
Ошибка на самом деле не происходит до тех пор, пока funcs[someIndex]
не будет выполнен ()
. Используя эту же логику, должно быть очевидно, что значение i
также не будет собрано до этой точки. Как только исходный цикл завершится, i++
добавит i
к значению 3
, что приведет к сбою условия i < 3
и завершению цикла. На этом этапе i
является 3
, поэтому, когда используется funcs[someIndex]()
, и i
оценивается, он равен 3 - каждый раз.
Чтобы пройти мимо этого, вы должны оценить i
, поскольку он встречается. Обратите внимание, что это уже произошло в форме funcs[i]
(где есть 3 уникальных индекса). Существует несколько способов захвата этого значения. Один из них - передать его в качестве параметра функции, которая уже несколько раз показана здесь.
Другой вариант - построить объект функции, который сможет закрыть эту переменную. Это может быть выполнено таким образом
funcs[i] = new function() {
var closedVariable = i;
return function(){
console.log("My value: " + closedVariable);
};
};
Бит опоздал на вечеринку, но я изучал этот вопрос сегодня и заметил, что многие ответы не полностью касаются того, как Javascript обрабатывает области, что по существу то, что это сводится к.
So как упоминалось многими другими, проблема в том, что внутренняя функция ссылается на одну и ту же переменную i
. Итак, почему бы нам просто не создать новую локальную переменную на каждой итерации и вместо этого иметь ссылку на внутреннюю функцию?
//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};
var funcs = {};
for (var i = 0; i < 3; i++) {
var ilocal = i; //create a new local variable
funcs[i] = function() {
console.log("My value: " + ilocal); //each should reference its own local variable
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Точно так же раньше, когда каждая внутренняя функция выдавала последнее значение, присвоенное i
, теперь каждая внутренняя функция просто выводит последнее значение, назначенное на ilocal
. Но не должна ли каждая итерация иметь свою собственную ilocal
?
Оказывается, в этом и проблема. Каждая итерация разделяет одну и ту же область, поэтому каждая итерация после первого просто переписывается ilocal
. Из MDN :
Важно: JavaScript не имеет области блока. Переменные, введенные с блоком, привязаны к содержащейся функции или скрипту, а эффекты их настройки сохраняются за пределами самого блока. Другими словами, операторы блоков не вводят область. Хотя «автономные» блоки являются допустимым синтаксисом, вы не хотите использовать автономные блоки в JavaScript, потому что они не делают то, что, по вашему мнению, они делают, если вы думаете, что они делают что-то вроде таких блоков на C или Java.
Повторяется для акцента:
JavaScript не имеет области блока. Переменные, введенные с блоком, привязаны к содержащейся функции или скрипту
. Мы можем увидеть это, проверив
ilocal
, прежде чем объявить его на каждой итерации:
//overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';}; var funcs = {}; for (var i = 0; i < 3; i++) { console.log(ilocal); var ilocal = i; }
Именно поэтому эта ошибка настолько сложна. Несмотря на то, что вы обновляете переменную, Javascript не будет вызывать ошибку, и JSLint даже не выдаст предупреждение. Именно поэтому лучший способ решить эту проблему - воспользоваться преимуществами закрытия, что по сути является идеей, что в Javascript внутренние функции имеют доступ к внешним переменным, потому что внутренние области «заключают» внешние области.
[/g13]
Это также означает, что внутренние функции «удерживают» внешние переменные и сохраняют их, даже если внешняя функция возвращается. Чтобы использовать это, мы создаем и вызываем функцию-оболочку только для создания новой области, объявляем
ilocal
в новой области и возвращаем внутреннюю функцию, которая используетilocal
(более подробное объяснение ниже):
//overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';}; var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function() { //create a new scope using a wrapper function var ilocal = i; //capture i into a local var return function() { //return the inner function console.log("My value: " + ilocal); }; })(); //remember to run the wrapper function } for (var j = 0; j < 3; j++) { funcs[j](); }
Создание внутренней функции внутри функции-обертки дает внутренней функции частную среду, доступ к которой может получить только «закрытие». Таким образом, каждый раз, когда мы вызываем функцию-оболочку, мы создаем новую внутреннюю функцию с ее собственной отдельной средой, гарантируя, что переменные
ilocal
не сталкиваются и не перезаписывают друг друга. Несколько незначительных оптимизаций дают окончательный ответ, который дали многие другие пользователи SO:
//overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';}; var funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = wrapper(i); } for (var j = 0; j < 3; j++) { funcs[j](); } //creates a separate environment for the inner function function wrapper(ilocal) { return function() { //return the inner function console.log("My value: " + ilocal); }; }
Обновить
С ES6 теперь mainstream, теперь мы можем использовать новое ключевое слово
let
для создания переменных с блочным диапазоном:
//overwrite console.log() so you can see the console output console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';}; var funcs = {}; for (let i = 0; i < 3; i++) { // use "let" to declare "i" funcs[i] = function() { console.log("My value: " + i); //each should reference its own local variable }; } for (var j = 0; j < 3; j++) { // we can use "var" here without issue funcs[j](); }
Посмотрите, как легко это сейчас! Для получения дополнительной информации см. этот ответ , на котором основана моя информация.
let
и const
. Если бы этот ответ расширился, включив его, это было бы гораздо более глобально полезным, на мой взгляд.
– Tiny Giant
27 December 2017 в 04:12
let
и связал более полное объяснение
– woojoo666
1 March 2018 в 23:44
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 ......)
– nutty about natty
14 May 2018 в 19:08
i
в своих тайм-аутах, поэтому вам не нужно закрывать
– woojoo666
3 June 2018 в 22:58
Прежде всего, поймите, что не так с этим кодом:
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function() { // and store them in funcs
console.log("My value: " + i); // each should log its value.
};
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Здесь, когда инициализируется массив funcs[]
, i
увеличивается, массив funcs
инициализируется и размер массива func
равен 3, поэтому i = 3,
. Теперь, когда вызывается funcs[j]()
, он снова использует переменную i
, которая уже была увеличена до 3.
Теперь, чтобы решить эту проблему, у нас есть много вариантов. Ниже приведены два из них:
i
с помощью let
или инициализировать новую переменную index
с помощью let
и сделать ее равной i
. Поэтому, когда вызов выполняется, index
будет использоваться, и его область действия закончится после инициализации. И для вызова снова будет инициализирован index
: var funcs = [];
for (var i = 0; i < 3; i++) {
let index = i;
funcs[i] = function() {
console.log("My value: " + index);
};
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
tempFunc
, который возвращает действительную функцию: var funcs = [];
function tempFunc(i){
return function(){
console.log("My value: " + i);
};
}
for (var i = 0; i < 3; i++) {
funcs[i] = tempFunc(i);
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
for (var i = 0; i < 3; i++) {
createfunc(i)();
}
function createfunc(i) {
return function(){console.log("My value: " + i);};
}
Попробуйте:
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = (function(index) {
return function() {
console.log("My value: " + index);
};
}(i));
}
for (var j = 0; j < 3; j++) {
funcs[j]();
}
Изменить (2014):
Лично я думаю, что более поздний ответ @ Aust об использовании .bind
- лучший способ сделать это сейчас. Также есть _.partial
lo-dash / underscore, когда вам не нужно или хотите взаимодействовать с bind
thisArg
.
i
в качестве аргумента index
функции.
– Jet Blue
26 July 2018 в 22:01
Ваш код не работает, потому что он делает это:
Create variable `funcs` and assign it an empty array;
Loop from 0 up until it is less than 3 and assign it to variable `i`;
Push to variable `funcs` next function:
// Only push (save), but don't execute
**Write to console current value of variable `i`;**
// First loop has ended, i = 3;
Loop from 0 up until it is less than 3 and assign it to variable `j`;
Call `j`-th function from variable `funcs`:
**Write to console current value of variable `i`;**
// Ask yourself NOW! What is the value of i?
Теперь вопрос в том, каково значение переменной i
при вызове функции? Поскольку первый цикл создается с условием i < 3
, он немедленно останавливается, когда условие ложно, поэтому оно i = 3
.
Вам нужно понять, что во время создания ваших функций ни один из их кодов не выполняется, он сохраняется только позже. И поэтому, когда они вызываются позже, интерпретатор выполняет их и спрашивает: «Каково текущее значение i
?»
Итак, ваша цель - сначала сохранить значение функции i
для функции и только после этого сохраните функцию до funcs
. Это можно сделать следующим образом:
var funcs = [];
for (var i = 0; i < 3; i++) { // let's create 3 functions
funcs[i] = function(x) { // and store them in funcs
console.log("My value: " + x); // each should log its value.
}.bind(null, i);
}
for (var j = 0; j < 3; j++) {
funcs[j](); // and now let's run each one to see
}
Таким образом, каждая функция будет иметь свою собственную переменную x
, и мы устанавливаем значение x
на значение i
на каждой итерации.
Это только один из нескольких способов решения этой проблемы.
.forEach()
- гораздо лучший вариант, если вы на самом деле ничего не картировали, и Дэрил предположил, что за 7 месяцев до того, как вы отправили сообщение, так что нечего удивляться.
– JLRishe
31 March 2015 в 19:59
const
дает тот же результат и должен использоваться, когда значение переменной не изменится. Однако использование const
внутри инициализатора цикла for неверно реализовано в Firefox и еще не исправлено. Вместо того, чтобы быть объявленным внутри блока, он объявляется вне блока, что приводит к повторному описанию переменной, что, в свою очередь, приводит к ошибке. Использование let
внутри инициализатора правильно реализовано в Firefox, поэтому не нужно беспокоиться об этом.
– Tiny Giant
27 December 2017 в 04:05
function createfunc(i) { return function() { console.log("My value: " + i); }; }
, поскольку использует переменнуюi
? – アレックス 28 March 2014 в 05:45Function.bind()
определенно предпочтительнее, см. stackoverflow.com/a/19323214/785541 . – Wladimir Palant 20 June 2014 в 13:21.bind()
является & quot; правильный ответ & quot; i> неправильным. У каждого из них есть свое место. С помощью.bind()
вы не можете связывать аргументы без привязки значенияthis
. Также вы получаете копию аргументаi
без возможности изменять его между вызовами, что иногда необходимо. Таким образом, это совершенно разные конструкции, не говоря уже о том, что реализации.bind()
были исторически медленными. Разумеется, на простом примере либо будет работать, но закрытие - важная концепция для понимания, и об этом и шла речь. – cookie monster 12 July 2014 в 03:35