Закрытие JavaScript внутри циклов - простой практический пример - PullRequest
2599 голосов
/ 15 апреля 2009

var funcs = [];
// let's create 3 functions
for (var i = 0; i < 3; i++) {
  // and store them in funcs
  funcs[i] = function() {
    // each should log its value.
    console.log("My value: " + i);
  };
}
for (var j = 0; j < 3; j++) {
  // and now let's run each one to see
  funcs[j]();
}

Это выводит это:

Моя ценность: 3
Моя ценность: 3
Моя ценность: 3

Принимая во внимание, что я хотел бы вывести:

Мое значение: 0
Мое значение: 1
Мое значение: 2


Та же проблема возникает, когда задержка запуска функции вызвана использованием прослушивателей событий:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}
<button>0</button>
<br />
<button>1</button>
<br />
<button>2</button>

… или асинхронный код, например используя обещания:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for (var i = 0; i < 3; i++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

Каково решение этой основной проблемы?

Ответы [ 43 ]

2 голосов
/ 05 мая 2016

И еще одно решение: вместо создания другого цикла просто привяжите 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
}

При связывании this также решает проблему.

1 голос
/ 05 сентября 2018

Просто замените ключевое слово var на let.

var - это область действия функции.

пусть является областью действия блока.

Когда вы начнете кодировать, цикл for будет повторяться и присваивать значению i значение 3, которое останется равным 3 во всем коде. Я предлагаю вам прочитать больше об областях в node (let, var, const и других)

funcs = [];
for (let i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] =async function() {          // and store them in funcs
    await 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
}
1 голос
/ 17 июля 2017

СЧЕТЧИК, ПРИМИТИВНЫЙ

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

// ****************************
// 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 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
0 голосов
/ 02 мая 2019

до ES5, эту проблему можно решить только с помощью closure .

Но теперь в ES6 у нас есть переменные области действия уровня блока. Изменение var на let в первом для цикла решит проблему.

var funcs = [];
for (let 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
}
0 голосов
/ 17 апреля 2019

При поддержке ES6 лучший способ для этого - использовать ключевые слова let и const для этого точного обстоятельства. Таким образом, var переменная get hoisted и в конце цикла значение i обновляется для всех closures ..., мы можем просто использовать let, чтобы установить переменную области видимости цикла следующим образом:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}
0 голосов
/ 11 ноября 2016

Давайте воспользуемся new Function. Таким образом, i перестает быть переменной замыкания и становится просто частью текста:

var funcs = [];
for (var i = 0; i < 3; i++) {
    var functionBody = 'console.log("My value: ' + i + '");';
    funcs[i] = new Function(functionBody);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();
}
0 голосов
/ 07 апреля 2019
  asyncIterable = [1,2,3,4,5,6,7,8];

  (async function() {
       for await (let num of asyncIterable) {
         console.log(num);
       }
    })();
0 голосов
/ 01 августа 2018

Это доказывает, насколько ужасен JavaScript в отношении того, как работает «замыкание» и «не замыкание».

В случае:

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.
  };
}

funcs [i] - глобальная функция, и 'console.log ("My value:" + i); " печатает глобальную переменную i

В случае

var funcs = [];

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

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

из-за этого скрученного замыкания javascript, 'console.log ("My value:" + i);' печатает i из внешней функции 'createfunc (i)'

все потому, что javascript не может создать что-то приличное, например, «статическую» переменную внутри функции, как это делает язык программирования C!

0 голосов
/ 25 мая 2018

Почему бы просто не вызывать каждую функцию внутри первого (и единственного) цикла сразу после их создания, например:

 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.
    };
    funcs[i]();// and now let's run each one to see
    }
0 голосов
/ 28 марта 2018

Хорошо. Я прочитал все ответы. Хотя здесь есть хорошее объяснение - я просто не мог заставить это работать. Поэтому я пошел смотреть в интернете. У человека по адресу https://dzone.com/articles/why-does-javascript-loop-only-use-last-value был ответ, которого здесь нет. Поэтому я решил опубликовать короткий пример. Это имело для меня больше смысла.

Суть в том, что команда LET хороша, но используется только сейчас. ОДНАКО команда LET на самом деле является просто комбинацией TRY-CATCH. Это работает вплоть до IE3 (я считаю). Использование комбинации TRY-CATCH - жизнь проста и хороша. Вероятно, почему люди EMCScript решили использовать его. Также не требуется функция setTimeout (). Так что время не потеряно. По сути, вам нужна одна комбинация TRY-CATCH для цикла FOR. Вот пример:

    for( var i in myArray ){
       try{ throw i }
       catch(ii){
// Do whatever it is you want to do with ii
          }
       }

Если у вас более одного цикла FOR, вы просто помещаете комбо TRY-CATCH для каждого. Кроме того, лично я всегда использую двойную букву любой переменной FOR, которую я использую. Так что «я» для «я» и так далее. Я использую эту технику в подпрограмме для отправки команд наведения мыши в другую подпрограмму.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...