Я смущен некоторыми аспектами асинхронных функций. Пожалуйста, объясните, что здесь происходит [dублировать]

Вам, по крайней мере, нужно будет отредактировать / etc / fstab, чтобы изменить ext4 на btrfs, изменить UUID (спасибо Riccardo за напоминание мне!) и, возможно, добавить или изменить параметры монтирования (и убедитесь, что он скопированы в initramfs тоже ...). Кроме того, необходимо внести изменения, необходимые для добавления раздела / boot.

Кроме того, помните, что btrfs все еще очень новый; более вероятно, что у него все еще много ошибок. Убедитесь, что вы не размещаете важные данные, если у вас нет резервных копий ...

2332
задан 22 June 2018 в 22:02

23 ответа

[F1]
1
ответ дан 15 August 2018 в 16:39

Благодаря тому, что 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/

Документы здесь:

const let

Остерегайтесь, хотя IE9-IE11 и Edge до Edge 14 поддерживают let, но получают вышеизложенное (они не создают новый i каждый раз, поэтому все вышеперечисленные функции будут записывать 3, как если бы мы использовали var). Edge 14, наконец, все исправляет.

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

chrome console window

Когда вы объявляете переменную с помощью 'var' вне функции, она становится глобальной переменной (вы можете проверить, набрав i или window.i в окне консоли. Он вернет 3 ). Анонимная функция, которую вы объявили, не будет вызывать и проверять значение внутри функции, если вы не вызываете функции. Когда вы вызываете функцию, console.log("My value: " + i) принимает значение из своего объекта Global и отображает результат.

Случай1

Теперь замените '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).

enter image description here [!d10]

Заключение:

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

Для более подробной информации о том, как работает более близко, пройдите через удивительный видеоурок enter image description here [!d10]

4
ответ дан 15 August 2018 в 16:39

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

После прочтения различных решений я хотел бы добавить, что причина, по которой эти решения работают, заключается в том, чтобы опираться на концепцию цепочки областей видимости. Это способ, которым JavaScript разрешает переменную во время выполнения.

Каждое определение функции формирует область, состоящую из всех локальных переменных, объявленных var и ее arguments. Если у нас есть внутренняя функция, определенная внутри другой (внешней) функции, это образует цепочку и будет использоваться во время выполнения. Когда функция выполняется, среда выполнения оценивает переменные путем поиска в цепочке областей видимости. Если переменная может быть найдена в определенной точке цепочки, она перестанет ее искать и использовать, иначе она будет продолжаться до тех пор, пока глобальная область не достигнет того, что принадлежит window.

В исходном коде:

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, если внутреннее исполнение выполнено.

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

340
ответ дан 15 August 2018 в 16:39
  • 1
    Мы редко пишем этот образец кода в реальном, но я думаю, что он служит хорошим примером для понимания фундаментального. Когда у нас есть смысл и как они связаны друг с другом, более понятно, почему естественным образом работают другие «современные» способы, такие как Array.prototype.forEach(function callback(el) {}): Обратный вызов, который передается, естественно формирует область обтекания с правильной привязкой к каждой итерации forEach. Поэтому каждая внутренняя функция, определенная в обратном вызове, сможет использовать правое значение el – wpding 26 April 2017 в 17:19
  • 2
    любое объяснение }(i));? – aswzen 6 April 2018 в 04:32
  • 3
    @aswzen Я думаю, что он передает i в качестве аргумента index функции. – Jet Blue 27 July 2018 в 01: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 (var 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
ответ дан 15 August 2018 в 16:39
  • 1
    Я изо всех сил пытался понять эту концепцию, пока не прочитал этот ответ. Он затрагивает действительно важный момент - значение i устанавливается в глобальную область. Когда цикл for завершается, глобальное значение i теперь равно 3. Следовательно, всякий раз, когда эта функция вызывается в массиве (используя, скажем, funcs[j]), i в этой функции ссылается на глобальную i (что равно 3). – Modermo 5 April 2017 в 05: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
ответ дан 15 August 2018 в 16:39

Что вам нужно понять, так это объем переменных в javascript, основанный на функции. Это важная разница, чем сказать c #, где у вас есть область блока, и просто копирование переменной в одну из них будет работать.

Обертывание ее в функции, которая оценивает возврат функции, как ответ афкакера, сделает трюк, так как переменная теперь имеет область видимости функции.

Вместо var используется ключевое слово let, которое позволит использовать правило области блока. В этом случае определение переменной внутри 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
ответ дан 15 August 2018 в 16:39

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

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

310
ответ дан 15 August 2018 в 16:39
  • 1
    Это то, что я делаю и в эти дни, мне также нравится lo-dash / underscore _.partial – Bjorn Tipling 8 December 2014 в 09:18
  • 2
    .bind() будет в значительной степени устаревать с функциями ECMAScript 6. Кроме того, это фактически создает две функции для каждой итерации. Сначала анонимный, затем тот, который генерируется .bind(). Лучше использовать его для создания вне цикла, затем .bind() внутри. – user 28 June 2015 в 06:29
  • 3
    Разве это не срабатывает JsHint - не выполняйте функции внутри цикла. ? Я пошел по этому пути, но после запуска инструментов качества кода его нет. – mekdev 28 June 2015 в 21:32
  • 4
    @squint @mekdev - Вы оба правы. Мой первоначальный пример был написан быстро, чтобы продемонстрировать, как используется bind. Я добавил еще один пример в ваших предложениях. – Aust 29 June 2015 в 19:23
  • 5
    Я думаю, вместо того, чтобы тратить деньги на вычисления над двумя циклами O (n), просто для (var i = 0; i & lt; 3; i ++) {log.call (this, i); } – user2290820 11 September 2015 в 15: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
ответ дан 15 August 2018 в 16:39
  • 1
    Прочитайте что-то подобное в одной книге. Я также предпочитаю это, так как вам не нужно прикасаться к существующему коду (насколько это важно), и становится очевидным, почему вы это сделали, как только вы узнали шаблон функции самозапуска: чтобы уловить эту переменную во вновь созданной объем. – DanMan 26 July 2013 в 16:18
  • 2
    @DanMan Спасибо. Самостоятельные анонимные функции - очень хороший способ преодолеть нехватку переменной уровня блока в javascript. – Kemal Dağ 26 July 2013 в 17:20
  • 3
    Самоназвание или самозапускание не является подходящим термином для этой техники, IIFE (Выражение с мгновенной вызывной функцией) более точно. Ссылка: benalman.com/news/2010/11/… – jherax 27 October 2015 в 08: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
ответ дан 15 August 2018 в 16:39

COUNTER PRIMITIVE

Давайте определим функции обратного вызова следующим образом:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

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

Чтобы передать и сохранить значение при определении обратного вызова, мы можем создать лексическую область ! d4], чтобы сохранить значение до вызова обратного вызова. Это можно сделать следующим образом:

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

Теперь в этом особенность: «Примитивы передаются по значению и копируются. Таким образом, когда ограничение определено, они сохраняют значение из предыдущего цикла».

COUNTER PRIMITIVE

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

// ****************************
// 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
ответ дан 15 August 2018 в 16:39
  • 1
    .forEach() - гораздо лучший вариант, если вы на самом деле ничего не картировали, и Дэрил предположил, что за 7 месяцев до того, как вы отправили сообщение, так что нечего удивляться. – JLRishe 31 March 2015 в 22:59
  • 2
    Этот вопрос не связан с циклом над массивом – jherax 27 October 2015 в 08:14
  • 3
    Ну, он хочет создать массив функций, этот пример показывает, как это сделать без привлечения глобальной переменной. – Christian Landgren 12 November 2015 в 01:25
  • 4
    @TinyGiant Пример с возвращаемой функцией по-прежнему оптимизирован для производительности. Я бы не стал прыгать на функции со стрелками, как у всех блоггеров JavaScript. Они выглядят классными и чистыми, но способствуют написанию функций inline вместо использования предопределенных функций. Это может быть неочевидная ловушка в горячих местах. Другая проблема заключается в том, что они не просто синтаксический сахар, потому что они выполняют ненужные привязки, создавая закрытие упаковки. – Pawel 27 December 2017 в 05:52
  • 5
    Предупреждение будущим читателям: Этот ответ неточно применяет термин Currying . «Currying - это когда вы разбиваете функцию, которая принимает несколько аргументов в ряд функций, которые принимают участие в аргументах.» . Этот код ничего не делает. Все, что вы сделали здесь, это взять код из принятого ответа, переместить некоторые вещи, изменить стиль и называть бит, а затем называть его currying, которого он категорически не является. – Tiny Giant 27 December 2017 в 06:36
  • 6
    const дает тот же результат и должен использоваться, когда значение переменной не изменится. Однако использование const внутри инициализатора цикла for неверно реализовано в Firefox и еще не исправлено. Вместо того, чтобы быть объявленным внутри блока, он объявляется вне блока, что приводит к повторному описанию переменной, что, в свою очередь, приводит к ошибке. Использование let внутри инициализатора правильно реализовано в Firefox, поэтому не нужно беспокоиться об этом. – Tiny Giant 27 December 2017 в 07:05

Использование выражения с мгновенной вызывной функцией - простейший и наиболее читаемый способ заключить индексную переменную:

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
ответ дан 15 August 2018 в 16:39
  • 1
    Для дальнейшей читаемости кода и во избежании путаницы в отношении того, что i является тем, я бы переименовал параметр функции в index. – Kyle Falconer 10 January 2014 в 22:45
  • 2
    Как бы вы использовали этот метод для определения массива funcs , описанного в исходном вопросе? – Nico 30 November 2014 в 17:17
  • 3
    @Nico Так же, как показано в исходном вопросе, за исключением того, что вместо i вы будете использовать index. – JLRishe 31 March 2015 в 23: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 в 12: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 в 13: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
}

Связывая это, решает проблему как хорошо.

1
ответ дан 15 August 2018 в 16:39

Я предпочитаю использовать функцию 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
ответ дан 15 August 2018 в 16:39
  • 1
    Предпочитаете это к чему? Это, кажется, комментарий в ответ на какой-то другой ответ. Он не затрагивает фактический вопрос вообще (поскольку вы не назначаете функцию, которую нужно вызывать позже, где угодно). – Quentin 17 December 2015 в 18:24
  • 2
    Теперь понятно? – Rax Wunter 17 December 2015 в 18:28
  • 3
    Это связано именно с упомянутой проблемой: как безопасно итеративно без проблем закрытия – Rax Wunter 17 December 2015 в 18:31
  • 4
    Теперь это не кажется существенно отличным от принятого ответа. – Quentin 17 December 2015 в 18:31
  • 5
    Нет. В принятом ответе предлагается использовать «некоторый массив», но мы имеем дело с диапазоном ответа, это абсолютно разные вещи, которые, к сожалению, не имеют хорошего решения в js, поэтому мой ответ пытается решить проблему на хорошем и практическом пути – Rax Wunter 17 December 2015 в 18: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
ответ дан 15 August 2018 в 16:39
  • 1
    Мне хотелось бы объяснить объяснение голосующих голосов. Код решает проблему. Было бы полезно знать, как потенциально улучшить код – Rune FS 18 June 2015 в 21:21
  • 2
    Что Query.range(0,3)? Это не является частью тегов для этого вопроса. Кроме того, если вы используете стороннюю библиотеку, вы можете предоставить ссылку на документацию. – jherax 27 October 2015 в 08:07
  • 3
    @jherax это или, конечно, очевидные улучшения. Спасибо за комментарий. Я мог бы поклясться, что уже есть ссылка. С учетом того, что сообщение было довольно бессмысленным, я думаю :). Моя первоначальная идея сохранить это была потому, что я не пытался использовать свою собственную библиотеку, но более декларативную идею. Однако в hinsight я полностью согласен с тем, что ссылка должна быть там – Rune FS 27 October 2015 в 14:17

Этот вопрос действительно показывает историю JavaScript! Теперь мы можем избежать блочного охвата функциями стрелок и обрабатывать петли непосредственно из узлов DOM с использованием методов Object.

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

4
ответ дан 15 August 2018 в 16:39

Основная проблема с кодом, показанным 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
ответ дан 15 August 2018 в 16:39

Бит опоздал на вечеринку, но я изучал эту проблему сегодня и заметил, что многие ответы не полностью касаются того, как 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++) {
    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]();
}

Именно поэтому эта ошибка настолько сложна. Несмотря на то, что вы обновляете переменную, Javascript не будет вызывать ошибку, и JSLint даже не выдаст предупреждение. Именно поэтому лучший способ решить эту проблему - воспользоваться преимуществами закрытия, что по сути является идеей, что в Javascript внутренние функции имеют доступ к внешним переменным, потому что внутренние области «заключают» внешние области.

[ ! d18]

Это также означает, что внутренние функции «удерживают» внешние переменные и сохраняют их, даже если внешняя функция возвращается. Чтобы использовать это, мы создаем и вызываем функцию-оболочку исключительно для создания новой области, объявляем 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++) {
    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]();
}

Создание внутренней функции внутри функции-обертки дает внутренней функции частную среду, доступ к которой может получить только «закрытие». Таким образом, каждый раз, когда мы вызываем функцию-оболочку, мы создаем новую внутреннюю функцию с ее собственной отдельной средой, гарантируя, что переменные 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++) {
    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]();
}

Обновить

Теперь, когда основной поток ES6, мы можем использовать новое ключевое слово 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]();
}

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

126
ответ дан 15 August 2018 в 16:39
  • 1
    Мне нравится, как вы объяснили путь IIFE. Я искал это. Спасибо. – CapturedTree 27 October 2017 в 00:45
  • 2
    Теперь в JavaScript используется ключевое слово с блоком let и const. Если бы этот ответ расширился, включив его, это было бы гораздо более глобально полезным, на мой взгляд. – Tiny Giant 27 December 2017 в 07:12
  • 3
    @TinyGiant, я добавил некоторую информацию о let и связал более полное объяснение – woojoo666 2 March 2018 в 02: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 в 22:08
  • 5
    @nuttyaboutnatty извините за такой поздний ответ. Не похоже, что код в вашем примере уже работает. Вы не используете i в своих тайм-аутах, поэтому вам не нужно закрывать – woojoo666 4 June 2018 в 01: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]();                        
}
5
ответ дан 15 August 2018 в 16:39

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

no array no extra for loop

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

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

http://jsfiddle.net/7P6EN/

23
ответ дан 15 August 2018 в 16:39
  • 1
    Ваше решение кажется корректным, но оно не использует функции, почему бы не просто console.log выход? Оригинальный вопрос касается создания анонимных функций, имеющих такое же закрытие. Проблема заключалась в том, что, поскольку у них есть одно замыкание, значение i одинаково для каждого из них. Надеюсь, вы поняли это. – Kemal Dağ 28 June 2015 в 11: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]();
}

Edit (2014):

Лично я думаю, что более недавний ответ @ Aust об использовании .bind - лучший способ сделать такие вещи Теперь. Также есть _.partial lo-dash / underscore, когда вам не нужно или хотите взаимодействовать с bind thisArg.

340
ответ дан 15 August 2018 в 16:39
  • 1
    любое объяснение }(i));? – aswzen 6 April 2018 в 04:32
  • 2
    @aswzen Я думаю, что он передает i в качестве аргумента index функции. – Jet Blue 27 July 2018 в 01: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 на каждой итерации.

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

77
ответ дан 15 August 2018 в 16:39
  • 1
    .forEach() - гораздо лучший вариант, если вы на самом деле ничего не картировали, и Дэрил предположил, что за 7 месяцев до того, как вы отправили сообщение, так что нечего удивляться. – JLRishe 31 March 2015 в 22:59
  • 2
    Этот вопрос не связан с циклом над массивом – jherax 27 October 2015 в 08:14
  • 3
    Ну, он хочет создать массив функций, этот пример показывает, как это сделать без привлечения глобальной переменной. – Christian Landgren 12 November 2015 в 01:25
  • 4
    @TinyGiant Пример с возвращаемой функцией по-прежнему оптимизирован для производительности. Я бы не стал прыгать на функции со стрелками, как у всех блоггеров JavaScript. Они выглядят классными и чистыми, но способствуют написанию функций inline вместо использования предопределенных функций. Это может быть неочевидная ловушка в горячих местах. Другая проблема заключается в том, что они не просто синтаксический сахар, потому что они выполняют ненужные привязки, создавая закрытие упаковки. – Pawel 27 December 2017 в 05:52
  • 5
    Предупреждение будущим читателям: Этот ответ неточно применяет термин Currying . «Currying - это когда вы разбиваете функцию, которая принимает несколько аргументов в ряд функций, которые принимают участие в аргументах.» . Этот код ничего не делает. Все, что вы сделали здесь, это взять код из принятого ответа, переместить некоторые вещи, изменить стиль и называть бит, а затем называть его currying, которого он категорически не является. – Tiny Giant 27 December 2017 в 06:36
  • 6
    const дает тот же результат и должен использоваться, когда значение переменной не изменится. Однако использование const внутри инициализатора цикла for неверно реализовано в Firefox и еще не исправлено. Вместо того, чтобы быть объявленным внутри блока, он объявляется вне блока, что приводит к повторному описанию переменной, что, в свою очередь, приводит к ошибке. Использование let внутри инициализатора правильно реализовано в Firefox, поэтому не нужно беспокоиться об этом. – Tiny Giant 27 December 2017 в 07:05

Другие вопросы по тегам:

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