Функция обратного вызова jquery работает только в последнем цикле - PullRequest
6 голосов
/ 13 октября 2009
    for(var i=0; i<barValues.length; i++) {


    actualBarHeight = Math.floor((barValues[i]/chartMaxY)*barchartHeight);

    var barChartID = "#barChart" + (i+1)
    $(barChartID + " .value span").css('background-color','transparent');
    $(barChartID + " img").animate({ 
            height: actualBarHeight
        }, 500, function(){$(barChartID + " .value span").css('background-color','white');}
    );

    $(barChartID + " .value span").html("$"+Math.floor(barValues[i]));
    $(barChartID + " .value").css("bottom",actualBarHeight+"px");
    $(barChartID + " .ylabel").html(chartMaxY);

};

Вышеупомянутый бит jQuery находится внутри цикла for. Каждая итерация цикла выполняет следующие действия:

  • устанавливает фон пролета
  • оживляет объект
  • по окончании сбрасывает фон пролета

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

Если я перемещу этот бит кода за пределы обратного вызова, то он будет влиять на каждый промежуток через каждую итерацию цикла for (но в этом случае не ожидает анимацию)

Я предполагаю, что проблема связана со сборкой селектора ВНУТРИ функции ВНУТРИ функции анимации. Есть ли какой-то плохой синтаксис в моей разметке?

РЕДАКТИРОВАТЬ (согласно предложению Русса, теперь я включаю полный цикл в приведенный выше пример)

Ответы [ 4 ]

16 голосов
/ 13 октября 2009

Это распространенная проблема, возникающая при объединении замыканий с циклами. JavaScript является языком позднего связывания, и циклы не вводят новую область видимости. Итак:

for (var i= 0; i<5; i++) {
    $('#thing'+i).click(function() {
        alert(i);
    });
}

В этом коде есть только одна i переменная. Он начинается в 0 и после завершения цикла присваивания остается в 5. Событие click для элемента #thing0 будет запускаться только после того, как завершит выполнение цикла, к этому моменту значение i будет 5. Вы не получите ожидаемое значение 0, которое вы могли ожидать.

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

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

$(barChartID + " img").animate({height: actualBarHeight}, 500, function(barChartID) {
    return function() {
        $(barChartID + " .value span").css('background-color','white');
    };
}(barChartID));

Подробнее о петлевых затворах.

4 голосов
/ 13 октября 2009

Это потому, что ваша функция обратного вызова имеет неявный указатель на переменную barChartID. Вот как работают крышки. Вам нужно создать новую копию текущего значения barChartID в каждой итерации. Один из способов исправить это - запустить тело цикла for внутри функции. Я видел этот паттерн в исключении из книги Джона Резига Тайны JavaScript ниндзя

for(var i=0; i<barValues.length; i++) function(i){
    ...
}(i);
3 голосов
/ 13 октября 2009

Поскольку вы используете jQuery, вы можете использовать функцию $ .each вместо цикла for. Обратный вызов, используемый вместо цикла, создаст новое замыкание.

$.each(barValues, function(i, barValue){
    actualBarHeight = Math.floor((barValue/chartMaxY)*barchartHeight);

    var barChartID = "#barChart" + (i+1)
    $(barChartID + " .value span").css('background-color','transparent');
    $(barChartID + " img").animate({ 
                    height: actualBarHeight
            }, 500, function(){$(barChartID + " .value span").css('background-color','white');}
    );

    $(barChartID + " .value span").html("$"+Math.floor(barValue));
    $(barChartID + " .value").css("bottom",actualBarHeight+"px");
    $(barChartID + " .ylabel").html(chartMaxY);
});
0 голосов
/ 13 октября 2009

Вы можете попробовать кэшировать селектор диапазона, например:

var barChartID = "#barChart" + (i+1)
var span = $(barChartID + " .value span");
span.css('background-color','transparent');
$(barChartID + " img").animate({ 
    height: actualBarHeight
}, 500, function() {
    span.css('background-color','white');
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...