Цитирование через массив функций и массив классов с использованием JQuery [duplicate]

Используйте display:inline-block с полем и медиа-запросом для IE6 / 7:

<html>
  <head>
    <style>
      div { display:inline-block; }
      /* IE6-7 */
      @media,
          {
          div { display: inline; margin-right:10px; }
          }
   </style>
  </head>
  <div>foo</div>
  <div>bar</div>
  <div>baz</div>
</html>
2413
задан Xufox 22 June 2018 в 19:02
поделиться

29 ответов

Ну, проблема в том, что переменная 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» останется таким, каким вы намеревались.


2015 Решение: forEach

. При относительно широкой доступности функции 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() дает вам аналогичная возможность.


Решение ES6: 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, наконец, получает это право.

1801
ответ дан Quentin 15 August 2018 в 14:58
поделиться
  • 1
    не закрывается function createfunc(i) { return function() { console.log("My value: " + i); }; }, поскольку использует переменную i? – アレックス 28 March 2014 в 05:45
  • 2
    К сожалению, этот ответ устарел, и никто не увидит правильный ответ внизу - с помощью Function.bind() определенно предпочтительнее, см. stackoverflow.com/a/19323214/785541 . – Wladimir Palant 20 June 2014 в 13:21
  • 3
    @Wladimir: Ваше предложение, что .bind() является & quot; правильный ответ & quot; неправильным. У каждого из них есть свое место. С помощью .bind() вы не можете связывать аргументы без привязки значения this. Также вы получаете копию аргумента i без возможности изменять его между вызовами, что иногда необходимо. Таким образом, это совершенно разные конструкции, не говоря уже о том, что реализации .bind() были исторически медленными. Разумеется, на простом примере либо будет работать, но закрытие - важная концепция для понимания, и об этом и шла речь. – cookie monster 12 July 2014 в 03:35
  • 4
    Пожалуйста, прекратите использовать эти хакеры для возврата, используйте [] .forEach или [] .map вместо этого, потому что они избегают повторного использования одних и тех же переменных области. – Christian Landgren 7 February 2015 в 11:23
  • 5
    @ChristianLandgren: Это полезно, если вы повторяете массив. Эти методы не являются «хаками». Это важные знания. – user 29 June 2015 в 16:31
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
}
1
ответ дан ashish yadav 15 August 2018 в 14:58
поделиться

Когда 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, наконец, получает это право.

121
ответ дан Ben McCormick 15 August 2018 в 14:58
поделиться
  • 1
    К сожалению, «let» по-прежнему не полностью поддерживается, особенно на мобильных устройствах. [Д0] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… – MattC 23 February 2016 в 18:47
  • 2
    По состоянию на июнь '16, , пусть поддерживается во всех основных версиях браузера, кроме iOS Safari, Opera Mini и Safari 9. Поддерживаются Evergreen браузеры. Вавилон будет превзойти его правильно, чтобы сохранить ожидаемое поведение без включения режима высокой совместимости. – Dan Pantry 22 June 2016 в 10:18
  • 3
    @DanPantry да о времени для обновления :) Обновлено, чтобы лучше отражать текущее состояние вещей, включая добавление упоминания о связях const, doc и лучшей совместимости. – Ben McCormick 27 June 2016 в 14:24
  • 4
    Не потому ли мы используем babel для перевода нашего кода, чтобы браузеры, не поддерживающие ES6 / 7, могли понять, что происходит? – pixel 67 19 March 2018 в 16:56

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

Вывод:

  1. Когда вы объявляете переменную с помощью 'var' вне функции, она становится глобальной переменной (вы можете проверить, введя i или window.i в окне консоли. return 3).
  2. Объявленная вами анонимная функция не вызовет и не проверит значение внутри функции, если вы не вызываете функции.
  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', и странно, что для каждой функции значение if i отличается (0, 1, 2).

Заключение:

Когда вы объявляете переменную, используя 'let' даже вне функции, но внутри цикла, эта переменная будет не будет глобальной переменной, она станет переменной уровня Block, которая доступна только для одной и той же функции. Именно поэтому мы получаем значение i для каждой функции при вызове функций.

Для получения более подробной информации о том, как работает ближе, пройдите через удивительный видеоурок https://youtu.be/71AtaJpJHw0

4
ответ дан Bimal Das 15 August 2018 в 14:58
поделиться

Вот еще одна вариация в технике, подобная 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., изменения его значения сохраняются между вызовами. Это может быть очень удобно.

48
ответ дан Boann 15 August 2018 в 14:58
поделиться
  • 1
    Спасибо, и ваше решение работает. Но я хотел бы спросить, почему это работает, но замена строки var и return не сработает? Благодаря! – midnite 3 December 2013 в 04:56
  • 2
    @midnite Если вы поменяли местами 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, когда внутреннее исполнение выполнено.

Более подробно можно найти здесь . В него входит общая ошибка при создании замыкания в цикле, как то, что мы имеем здесь, а также почему мы нуждаемся в закрытии и рассмотрении эффективности.

340
ответ дан Community 15 August 2018 в 14:58
поделиться
  • 1
    Мы редко пишем этот образец кода в реальном, но я думаю, что он служит хорошим примером для понимания фундаментального. Когда у нас есть смысл и как они связаны друг с другом, более понятно, почему естественным образом работают другие «современные» способы, такие как Array.prototype.forEach(function callback(el) {}): обратный вызов, который передается, естественно формирует область обтекания с правильной привязкой к каждой итерации forEach. Поэтому каждая внутренняя функция, определенная в обратном вызове, сможет использовать правое значение el – wpding 26 April 2017 в 14:19
  • 2
    любое объяснение }(i));? – aswzen 6 April 2018 в 01:32
  • 3
    @aswzen Я думаю, что он передает 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 в нашем случае считается объявленной в фигурных скобках.)

16
ответ дан Costa 15 August 2018 в 14:58
поделиться
  • 1
    Я изо всех сил пытался понять эту концепцию, пока не прочитал этот ответ. Он затрагивает действительно важный момент - значение 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
}

Печатает:

My value: 0
My value: 1
My value: 2
19
ответ дан Daryl 15 August 2018 в 14:58
поделиться

Что вам нужно понять, так это объем переменных в 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]();                        
}
60
ответ дан eglasius 15 August 2018 в 14:58
поделиться

Еще один способ, который еще не упоминался, - использование 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]();
}

310
ответ дан Håken Lid 15 August 2018 в 14:58
поделиться
  • 1
    Это то, что я делаю и в эти дни, мне также нравится lo-dash / underscore _.partial – Bjorn Tipling 8 December 2014 в 06:18
  • 2
    .bind() будет в значительной степени устаревать с функциями ECMAScript 6. Кроме того, это фактически создает две функции для каждой итерации. Сначала анонимный, затем тот, который генерируется .bind(). Лучше использовать его для создания вне цикла, затем .bind() внутри. – user 28 June 2015 в 03:29
  • 3
    Разве это не срабатывает JsHint - не выполняйте функции внутри цикла. ? Я пошел по этому пути, но после запуска инструментов качества кода его нет. – mekdev 28 June 2015 в 18:32
  • 4
    @squint @mekdev - Вы оба правы. Мой первоначальный пример был написан быстро, чтобы продемонстрировать, как используется bind. Я добавил еще один пример в ваших предложениях. – Aust 29 June 2015 в 16:23
  • 5
    Я думаю, вместо того, чтобы тратить деньги на вычисления над двумя циклами O (n), просто для (var i = 0; i & lt; 3; i ++) {log.call (this, i); } – user2290820 11 September 2015 в 12:14

Самое простое решение:

Вместо использования:

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 отличается для каждой функции, определенной внутри анонимной функции.

Это решение похоже на любую проблему, так как оно потребует минимальных изменений в исходном коде, страдающем из этого вопроса. Фактически, это по дизайну, это не должно быть проблемой вообще!

40
ответ дан Kemal Dağ 15 August 2018 в 14:58
поделиться
  • 1
    Прочитайте что-то подобное в одной книге. Я также предпочитаю это, так как вам не нужно прикасаться к существующему коду (насколько это важно), и становится очевидным, почему вы это сделали, как только вы узнали шаблон функции самозапуска: чтобы уловить эту переменную во вновь созданной объем. – DanMan 26 July 2013 в 12:18
  • 2
    @DanMan Спасибо. Самостоятельные анонимные функции - очень хороший способ преодолеть нехватку переменной уровня блока в javascript. – Kemal Dağ 26 July 2013 в 13:20
  • 3
    Самоназвание или самозапускание не является подходящим термином для этой техники, IIFE (Выражение с мгновенной вызывной функцией) более точно. Ссылка: benalman.com/news/2010/11/… – jherax 27 October 2015 в 05:29

Это описывает распространенную ошибку с использованием замыканий в 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 ловушки закрытия и использование

44
ответ дан Lucas 15 August 2018 в 14:58
поделиться

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
77
ответ дан Nae 15 August 2018 в 14:58
поделиться
  • 1
    .forEach() - гораздо лучший вариант, если вы на самом деле ничего не картировали, и Дэрил предположил, что за 7 месяцев до того, как вы отправили сообщение, так что нечего удивляться. – JLRishe 31 March 2015 в 19:59
  • 2
    Этот вопрос не связан с циклом над массивом – jherax 27 October 2015 в 05:14
  • 3
    Ну, он хочет создать массив функций, этот пример показывает, как это сделать без привлечения глобальной переменной. – Christian Landgren 11 November 2015 в 22:25
  • 4
    @TinyGiant Пример с возвращаемой функцией по-прежнему оптимизирован для производительности. Я бы не стал прыгать на функции со стрелками, как у всех блоггеров JavaScript. Они выглядят классными и чистыми, но способствуют написанию функций inline вместо использования предопределенных функций. Это может быть неочевидная ловушка в горячих местах. Другая проблема заключается в том, что они не просто синтаксический сахар, потому что они выполняют ненужные привязки, создавая закрытие упаковки. – Pawel 27 December 2017 в 02:52
  • 5
    Предупреждение будущим читателям: Этот ответ неточно применяет термин Currying . "Currying - это когда вы разбиваете функцию, которая принимает несколько аргументов в ряд функций, которые принимают часть аргументов. & quot; . Этот код ничего не делает. Все, что вы сделали здесь, это взять код из принятого ответа, переместить некоторые вещи, изменить стиль и называть бит, а затем называть его currying, которого он категорически не является. – Tiny Giant 27 December 2017 в 03:36
  • 6
    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.

234
ответ дан neurosnap 15 August 2018 в 14:58
поделиться
  • 1
    Для дальнейшей читаемости кода и во избежании путаницы в отношении того, что i является тем, я бы переименовал параметр функции в index. – Kyle Falconer 10 January 2014 в 18:45
  • 2
    Как бы вы использовали этот метод для определения массива funcs , описанного в исходном вопросе? – Nico 30 November 2014 в 14:17
  • 3
    @Nico Так же, как показано в исходном вопросе, за исключением того, что вместо i вы будете использовать index. – JLRishe 31 March 2015 в 20:54
  • 4
    @JLRishe 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
  • 5
    @Nico В конкретном случае OP они просто перебирают числа, поэтому для .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 связывая это, решает также проблему.

1
ответ дан pixel 67 15 August 2018 в 14:58
поделиться

Я предпочитаю использовать функцию 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 менее чудовищным, чем другие решения.

2
ответ дан Ram 15 August 2018 в 14:58
поделиться
  • 1
    Предпочитаете это к чему? Это, кажется, комментарий в ответ на какой-то другой ответ. Он не затрагивает фактический вопрос вообще (поскольку вы не назначаете функцию, которую нужно вызывать позже, где угодно). – Quentin 17 December 2015 в 15:24
  • 2
    Теперь понятно? – Rax Wunter 17 December 2015 в 15:28
  • 3
    Это связано именно с упомянутой проблемой: как безопасно итеративно без проблем закрытия – Rax Wunter 17 December 2015 в 15:31
  • 4
    Теперь это не кажется существенно отличным от принятого ответа. – Quentin 17 December 2015 в 15:31
  • 5
    Нет. В принятом ответе предлагается использовать «некоторый массив», но мы имеем дело с диапазоном ответа, это абсолютно разные вещи, которые, к сожалению, не имеют хорошего решения в js, поэтому мой ответ пытается решить проблему на хорошем и практическом пути – Rax Wunter 17 December 2015 в 15:34

Вы можете использовать декларативный модуль для списков данных, таких как query-js (*). В этих ситуациях я лично считаю декларативный подход менее неожиданным

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

. Затем вы можете использовать свой второй цикл и получить ожидаемый результат, или вы могли бы сделать

funcs.iterate(function(f){ f(); });

(*) Я автор запросов-js и поэтому склонен к его использованию, поэтому не принимайте мои слова в качестве рекомендации для указанной библиотеки только для декларативного подхода:)

1
ответ дан Rune FS 15 August 2018 в 14:58
поделиться
  • 1
    Мне хотелось бы объяснить объяснение голосующих голосов. Код решает проблему. Было бы полезно знать, как потенциально улучшить код – Rune FS 18 June 2015 в 18:21
  • 2
    Что Query.range(0,3)? Это не является частью тегов для этого вопроса. Кроме того, если вы используете стороннюю библиотеку, вы можете предоставить ссылку на документацию. – jherax 27 October 2015 в 05:07
  • 3
    @jherax это или, конечно, очевидные улучшения. Спасибо за комментарий. Я мог бы поклясться, что уже есть ссылка. С учетом того, что сообщение было довольно бессмысленным, я думаю :). Моя первоначальная идея сохранить это была потому, что я не пытался использовать свою собственную библиотеку, но более декларативную идею. Однако в hinsight я полностью согласен с тем, что ссылка должна быть там – Rune FS 27 October 2015 в 11:17

Этот вопрос действительно показывает историю 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>

4
ответ дан sidhuko 15 August 2018 в 14:58
поделиться

Основная проблема с кодом, показанным 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 уникальных индекса). Существует несколько способов захвата этого значения. Один из них - передать его в качестве параметра функции, которая уже несколько раз показана здесь.

Другой вариант - построить объект функции, который сможет закрыть эту переменную. Это может быть выполнено таким образом

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};
20
ответ дан Travis J 15 August 2018 в 14:58
поделиться

Бит опоздал на вечеринку, но я изучал этот вопрос сегодня и заметил, что многие ответы не полностью касаются того, как 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 внутренние функции имеют доступ к внешним переменным, потому что внутренние области «заключают» внешние области.

Closures [/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]();
}

Посмотрите, как легко это сейчас! Для получения дополнительной информации см. этот ответ , на котором основана моя информация.

126
ответ дан woojoo666 15 August 2018 в 14:58
поделиться
  • 1
    Мне нравится, как вы объяснили путь IIFE. Я искал это. Спасибо. – CapturedTree 26 October 2017 в 21:45
  • 2
    Теперь в JavaScript используется ключевое слово с блоком let и const. Если бы этот ответ расширился, включив его, это было бы гораздо более глобально полезным, на мой взгляд. – Tiny Giant 27 December 2017 в 04:12
  • 3
    @TinyGiant, я добавил некоторую информацию о let и связал более полное объяснение – woojoo666 1 March 2018 в 23:44
  • 4
    @ woojoo666 Будет ли ваш ответ работать для вызова двух чередующихся URL-адресов в цикле следующим образом: 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
  • 5
    @nuttyaboutnatty извините за такой поздний ответ. Не похоже, что код в вашем примере уже работает. Вы не используете 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.

Теперь, чтобы решить эту проблему, у нас есть много вариантов. Ниже приведены два из них:

  1. Мы можем инициализировать 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]();                        
    }
    
  2. Другой вариант может состоять в том, чтобы ввести 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]();                        
    }
    
5
ответ дан YakovL 15 August 2018 в 14:58
поделиться

попробуйте эту более короткую

  • no array
  • no extra для цикла

for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

] http://jsfiddle.net/7P6EN/

23
ответ дан yilmazburk 15 August 2018 в 14:58
поделиться
  • 1
    Ваше решение кажется корректным, но оно не использует функции, почему бы не просто console.log выход? Оригинальный вопрос касается создания анонимных функций, имеющих такое же закрытие. Проблема заключалась в том, что, поскольку у них есть одно замыкание, значение i одинаково для каждого из них. Надеюсь, вы поняли это. – Kemal Dağ 28 June 2015 в 08:51

Попробуйте:

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.

340
ответ дан Community 15 August 2018 в 14:58
поделиться
  • 1
    любое объяснение }(i));? – aswzen 6 April 2018 в 01:32
  • 2
    @aswzen Я думаю, что он передает i в качестве аргумента index функции. – Jet Blue 26 July 2018 в 22:01

Другой способ сказать, что i в вашей функции привязан во время выполнения функции, а не время создания функции.

Когда вы создаете закрытие, i является ссылкой на переменную, определенную во внешней области, а не на ее копию, как это было при создании закрытия. Это будет оценено во время выполнения.

Большинство других ответов предоставляют способы обойти, создав другую переменную, которая не изменит значение для вас.

Просто подумал Я бы добавил объяснение для ясности. Для решения, лично, я бы пошел с Harto, так как это самый бескорыстный способ сделать это из ответов здесь. Любой из опубликованных кодов будет работать, но я бы выбрал фабрику закрытия, чтобы написать кучу комментариев, чтобы объяснить, почему я объявляю новую переменную (Freddy и 1800) или имеет странный встроенный синтаксис закрытия (apphacker).

77
ответ дан Nae 15 August 2018 в 14:58
поделиться

Ваш код не работает, потому что он делает это:

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 на каждой итерации.

Это только один из нескольких способов решения этой проблемы.

10
ответ дан Nae 15 August 2018 в 14:58
поделиться
  • 1
    .forEach() - гораздо лучший вариант, если вы на самом деле ничего не картировали, и Дэрил предположил, что за 7 месяцев до того, как вы отправили сообщение, так что нечего удивляться. – JLRishe 31 March 2015 в 19:59
  • 2
    Этот вопрос не связан с циклом над массивом – jherax 27 October 2015 в 05:14
  • 3
    Ну, он хочет создать массив функций, этот пример показывает, как это сделать без привлечения глобальной переменной. – Christian Landgren 11 November 2015 в 22:25
  • 4
    @TinyGiant Пример с возвращаемой функцией по-прежнему оптимизирован для производительности. Я бы не стал прыгать на функции со стрелками, как у всех блоггеров JavaScript. Они выглядят классными и чистыми, но способствуют написанию функций inline вместо использования предопределенных функций. Это может быть неочевидная ловушка в горячих местах. Другая проблема заключается в том, что они не просто синтаксический сахар, потому что они выполняют ненужные привязки, создавая закрытие упаковки. – Pawel 27 December 2017 в 02:52
  • 5
    Предупреждение будущим читателям: Этот ответ неточно применяет термин Currying . "Currying - это когда вы разбиваете функцию, которая принимает несколько аргументов в ряд функций, которые принимают часть аргументов. & quot; . Этот код ничего не делает. Все, что вы сделали здесь, это взять код из принятого ответа, переместить некоторые вещи, изменить стиль и называть бит, а затем называть его currying, которого он категорически не является. – Tiny Giant 27 December 2017 в 03:36
  • 6
    const дает тот же результат и должен использоваться, когда значение переменной не изменится. Однако использование const внутри инициализатора цикла for неверно реализовано в Firefox и еще не исправлено. Вместо того, чтобы быть объявленным внутри блока, он объявляется вне блока, что приводит к повторному описанию переменной, что, в свою очередь, приводит к ошибке. Использование let внутри инициализатора правильно реализовано в Firefox, поэтому не нужно беспокоиться об этом. – Tiny Giant 27 December 2017 в 04:05
10
ответ дан Nae 5 September 2018 в 14:05
поделиться
10
ответ дан Nae 5 September 2018 в 14:05
поделиться
11
ответ дан Nae 28 October 2018 в 22:22
поделиться
11
ответ дан Nae 28 October 2018 в 22:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: