Javascript: каждый обработчик событий, определенный в for-loop, одинаков, использует значения последней итерации

#javascript #scope

#javascript #область действия

Вопрос:

У меня проблемы с пониманием правил области видимости в Javascript.

В приведенном ниже примере я бы предположил, что переменная url области видимости является частной в for-loop. И что функция onload-event увидит этот частный экземпляр.

Но, похоже, все работает не так — предупреждение будет всплывать с последним URL дважды.

Если кто-нибудь может пояснить, что происходит, я буду благодарен.

 <html>
<head>
</head>
<body>
<script type="text/javascript">
    var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
    for (var i=0;i<testArray.length;i  ){
        var img = new Image();
        var url = testArray[i];
        img.onload = function(){
            alert(url);
        }
        img.src = url;
    }
</script>
</body>
</html>
  

Ответ №1:

JavaScript не имеет области видимости блока.

Единственный способ создать новую область видимости переменной — в функции.

 var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];

function createImg( url ) {
    var img = new Image();

    img.onload = function(){
        alert(url);
    }
    img.src = url;
    return img;
}
for (var i=0;i<testArray.length;i  ){
    var img = createImg(testArray[i]);
}
  

Передавая testArray[i] функции, которая создает и возвращает новое изображение, убедитесь, что url ссылка в onload обработчике будет той, которая была ограничена в функции.


Редактировать:

В конечном счете, вы бы никогда этого не сделали, если все, что вам нужно, это доступ к url .

Вы бы просто получили его из свойства элемента через this .

 function onloadHandler(){
    alert( this.src );  // <--- get the url from the .src property!
}

var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i  ){
    var img = new Image();
    var url = testArray[i];
    img.onload = onloadHandler;
    img.src = url;
}
  

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

Комментарии:

1. 1 Специально для перемещения определения функции из цикла.

2. Вот почему выгодно использовать [].map(function(x){...}) or [].forEach(function(x){...}) , которые есть в стандарте javascript, поскольку вам все равно нужно будет определить эти функции.

Ответ №2:

Javascript не ограничен блоками и, следовательно, требует новой функции каждый раз, когда вам нужна новая область видимости. Смотрите ответ Патрика ду.

Вот почему выгодно использовать [].map(function(x){...}) or [].forEach(function(x){...}) , которые есть в стандарте javascript, поскольку вам все равно нужно будет определить эти функции.

 var imageArray = urlArray.map(function(url) {
    var image = new Image();
    image.src = url;
    image.onload = function() {
        alert(url);
    };

    return image;
});
  

Комментарии:

1. 1 для .map() и .forEach() , хотя вам нужно было бы добавить для них поддержку в IE9.

2. Поддержка IE9 ниже может быть решена с помощью прокладки .

Ответ №3:

Попробуйте это 🙂

 var testArray = ["http://g0.gstatic.com/images/icons/onebox/weather_rain-40.png", "http://g0.gstatic.com/images/icons/onebox/weather_scatteredshowers-40.png"];
for (var i=0;i<testArray.length;i  ){
    var img = new Image();
    var url = testArray[i];
    img.onload = function(){
        alert([img.src, url, i]);
    }
    img.src = url;
}