JavaScript для индексации индекса цикла

Я относительно новичок в JS, так что это может быть распространенной проблемой, но я заметил что-то странное при работе с циклами и функцией onclick. Я смог воспроизвести проблему с помощью этого кода:

<html>
<head>

<script type="text/javascript">
window.onload = function () {
    var buttons = document.getElementsByTagName('a');
    for (var i=0; i<2; i++) {
        buttons[i].onclick = function () {
            alert(i);
            return false;
        }
    }
}

</script>

</head>

<body>
<a href="">hi</a>
<br />
<a href="">bye</a>

</body>

</html>

При нажатии на ссылки я ожидаю получить "0" и "1", но вместо этого я получаю "2" для них обоих. Почему это?

Кстати, мне удалось решить мою конкретную проблему, используя ключевое слово 'this', но мне все еще интересно, что стоит за этим поведением.

Ответы

Ответ 1

У вас очень распространенная проблема закрытия в цикле for.

Переменные, заключенные в закрытие, имеют одну и ту же единую среду, поэтому к моменту выполнения обратного вызова onclick цикл запустил свой курс, а переменная i останется указателем на последнюю запись.

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

function makeOnClickCallback(i) {  
   return function() {  
      alert(i);
      return false;
   };  
} 

var i;

for (i = 0; i < 2; i++) {
    buttons[i].onclick = makeOnClickCallback(i);
}

Это может быть довольно сложная тема, если вы не знакомы с тем, как работают замыкания. Вы можете просмотреть следующую статью Mozilla для краткого введения:


Примечание. Я также предлагаю не использовать var внутри цикла for, потому что это может обмануть вас, полагая, что переменная i имеет область блока, когда, с другой стороны, переменная i так же как и переменная buttons, ограниченная внутри функции.

Ответ 2

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

window.onload = function () {
    var buttons = document.getElementsByTagName('a');
    for (var i=0; i<2; i++) {
        (function (i) {
            buttons[i].onclick = function () {
                alert(i);
                return false;
            }
        })(i);
    }
}

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