2015-09-23 4 views
2

Я смотрел Javascript Основы Трэвис Тидуэлл, где он объяснил этот кусок кода:Javascript область видимости функции [Javascript Основы]

(function() { 
    var messages = ['hello', 'there']; 

    for (var i in messages) { 
     setTimeout(function() { 
      console.log(messages[i]); 
     }, 10); 
    }; 
})(); 

Это эхо «там» дважды в консоли, но я до сих пор не понимаю, именно поэтому. Может ли кто-нибудь пройти через этот кусок javascript со мной шаг за шагом?

+3

Престола: http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example –

+0

Это печально известная проблема «замыкания внутри петли». Вы найдете много информации, если будете искать это. По сути, причина такого поведения заключается в том, что цикл for уже завершен к моменту запуска тайм-аутов. В этот момент времени 'i' имеет значение последнего цикла для обоих тайм-аутов. – devnull69

ответ

5

Каждый раз, когда код проходит цикл, он устанавливает обработчик событий так, что после прохождения 10 мс он регистрирует значение messages[i].

Перед 10мс прошел по любому из этих таймаутов, значение i было изменено (с помощью петли for) к 1 (потому что это последнее имя свойства в массиве).

Первый тайм-аут затем выдает messages[1], затем второй тайм-аут messages[1].


  1. Массив создается и хранится в messages
  2. i установлен в 0 и тайм-аут устанавливается
  3. i установлен в 1 и тайм-аут устанавливается
  4. Первый тайм-аут прогонов функции , i по-прежнему 1
  5. Выполняется второй тайм-аут, i i s still 1
+0

Почему поведение такое же, если вы замените «10» на «0»? – johnnyRose

+3

Браузеры @johnnyRose устанавливают минимальное значение таймаута, которое обычно составляет более 10 миллисекунд. – Pointy

+0

@johnnyRose: Потому что 'setTimeout (..., 0)' не означает, что он сразу запускается ". Он будет в основном подталкивать его к нижней части очереди выполнения и запускать его * после завершения цикла. –

3

У JavaScript есть область функций, а не область блока, как на других языках. Таким образом, на самом деле существует только одна переменная i. К моменту, когда вызывается код внутри setTimeout, i уже установлен последним индексом массива.

Вскоре в ECMAScript 6 мы можем объявить переменные с блочным диапазоном с let. Смотрите здесь: Mozilla Reference: let

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

(function() { 
 
    var messages = ['hello', 'there']; 
 

 
    for (var i in messages) { 
 
    (function(currentIndex) { 
 
     setTimeout(function() { 
 
     logToOutput(messages[currentIndex]); 
 
     }, 10); 
 
    })(i); 
 
    }; 
 
})(); 
 

 
function logToOutput(msg) { 
 
    document.getElementById("output").innerHTML += msg + "<br>"; 
 
}
<div id="output"></div>

Смежные вопросы