Проблема setTimeout и анонимной функции

Это мой код, SetOpacity вызывается с неправильными значениями, почему?

function SetOpacity(eID, opacity){                  
   eID.style.opacity = opacity / 100;
   eID.style.filter = 'alpha(opacity=' + opacity + ')';
}
function fade(eID, startOpacity, endOpacity){           
    var timer = 0;
    if (startOpacity < endOpacity) { 
       for (var i = startOpacity; i <= endOpacity; i++) {
           setTimeout(function() {SetOpacity(eID, i);}, timer * 30);
           timer++;
        }
    }           
}

Ответ 1

Это должно работать:

for (var i = startOpacity; i <= endOpacity; i++) {
    (function(opacity) {
        setTimeout(function() {SetOpacity(eID, opacity);}, timer * 30);
    })(i);
    timer++;
}

Это работает следующим образом:

  • внутри цикла вы создаете анонимную функцию (function(...){...}) и сразу вызываете ее с параметром (вот почему есть скобки вокруг function(){}, поэтому вы можете называть его добавлением () в конце и параметрами прохода) Параметры
  • переданные этой анонимной функции (значение i, которое является opacity внутри функции) являются локальными для этой анонимной функции, поэтому они не меняются при следующих шагах цикла, и вы можете безопасно передавать их другому анонимная функция (это при setTimeout)

Ваша оригинальная версия не сработала, потому что:

  • ваша функция, переданная в setTimeout, содержит ссылку на переменную i (а не ее значение), и она получает значение, когда эта функция вызывается, а не время добавления ее в setTimeout
  • значение этой переменной изменяется в цикле, и до получения первого setTimeout оно получает значение endOpacity (последнее значение из цикла for)

К сожалению, JavaScript имеет только область функций, поэтому он не будет работать, если вы создаете переменную внутри цикла и назначаете новое фактическое значение, потому что всякий раз, когда есть некоторая внутренняя функция var, эти переменные создаются во время выполнения функции ( и получите значение undefined по умолчанию). Единственный (простой) способ создания новой области - создать функцию (может быть анонимной) и создать в ней новые переменные (параметры тоже являются переменными)

Ответ 2

Это проблема с закрытием. К моменту запуска функции i уже находится в endOpacity. Это будет работать, создав еще одно закрытие:

function SetOpacityTimeout(eID, opacity, timer){
  setTimeout(function() {SetOpacity(eID, opacity);}, timer * 30);
}

function fade(eID, startOpacity, endOpacity){           
    var timer = 0;
    if (startOpacity < endOpacity) {
       for (var i = startOpacity; i <= endOpacity; i++) {
          SetOpacityTimeout(eID,i,timer);
          timer++;
        }
    }           
}

Ответ 3

Коби имеет правильную идею по проблеме. Я предлагаю вам использовать интервал вместо этого.

Вот пример: (Ваша функция SetOpacity остается неизменной, я оставил ее здесь.)

function fade(eID, startOpacity, endOpacity){
    var opacity = startOpacity;
    SetOpacity(eID, opacity);

    var interval = window.setInterval(function(){
        opacity++;
        SetOpacity(eID, opacity);

        // Stop the interval when done
        if (opacity === endOpacity)
            window.clearInterval(interval);
    }, 30);
}

Ответ 4

Это пример, который я использовал с jquery. "menuitem" - это itemclass, а jquery проверяет класс "recentOut", чтобы увидеть, нужно ли ему выполнить резервное копирование.

Код говорит сам за себя.

$(".menuitem").mouseenter(
function(){
    $(this).addClass("over").removeClass("out").removeClass("recentlyOut");
    $(this).children(".sub").slideDown();
}); 
    $(".menuitem").mouseleave(
function(){

    $(this).addClass("out").addClass("recentlyOut").removeClass("over");
    setTimeout(function()
        {
            var bool = $(".recentlyOut").hasClass("over");
            if (!bool)
            {
    $(".recentlyOut").removeClass("recentlyOut").children(".sub").slideUp();
            }
        }
    , 400);
}
    );