JavaScript: как передать другой объект в обработчики setTimeout, созданные в цикле? - PullRequest
1 голос
/ 10 августа 2010

Я пытаюсь написать несколько JS, реплицирующих функции jQuery fadeIn и fadeOut.Вот код, который у меня есть:

function fadeIn(elem, d, callback)
{

    var duration = d || 1000;
    var steps = Math.floor(duration / 50);
    setOpacity(elem,0);
    elem.style.display = '';
    for (var i = 1; i <= steps; i++)
    {
        console.log(i/steps + ', ' + (i/steps) * duration);
        setTimeout('setOpacity("elem", '+(i / steps)+' )', (i/steps) * duration);
    }
    if (callback)
        setTimeout(callback,d);
}
function setOpacity(elem, level)
{
    console.log(elem);
    return;
    elem.style.opacity = level;
    elem.style.MozOpacity = level;
    elem.style.KhtmlOpacity = level;
    elem.style.filter = "alpha(opacity=" + (level * 100) + ");";
}

У меня проблемы с первым вызовом setTimeout - мне нужно передать объект 'elem' (который является элементом DOM) в функцию setOpacity.Передача переменной 'level' работает просто отлично ... однако я получаю сообщение об ошибке "elem is notfined".Я думаю, это потому, что к тому времени, когда на самом деле выполняются любые вызовы setOpacity, начальная функция fadeIn завершена, и поэтому переменная elem больше не существует.

Чтобы смягчить это, я попробовал другой подход:

setTimeout(function() { setOpacity(elem, (i / steps));}, (i/steps) * duration);

Проблема теперь в том, что при вызове функции (i / steps) теперь всегда 1,05 вместо увеличения от 0 до 1.

Как я могу передать рассматриваемый объект в setOpacity при правильномповысить уровень непрозрачности?

Ответы [ 2 ]

9 голосов
/ 10 августа 2010

Ваш "другой подход" правильный, вот как это обычно делается.

А что касается проблемы i всегда постоянной, вот как работают замыкания!Видите ли, когда вы создаете эту функцию, которая делает что-то с i (например, function() { alert(i); }), эта функция, как говорится, 'захватывает' или 'связывает' переменная i, так что переменная i не умирает после завершения цикла, но продолжает жить и на нее все еще ссылаются из этой функции.

Чтобы продемонстрировать эту концепцию, рассмотрим следующий код:

var i = 5;
var fn = function() { alert(i); };

fn();  // displays "5"

i = 6;
fn();  // displays "6"

Когда оно написано таким образом, концепция становится немного более очевидной, не так ли?Поскольку вы изменяете переменную в цикле, после завершения цикла переменная сохраняет свое последнее значение (1+steps) - и это именно то, что видит ваша функция, когда начинает выполняться.

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

function createFn( theArgument )
{
    return function() { alert( theArgument ); };
}

var i = 5;
var fn = createFn( i );

fn();  // displays "5"

i = 6;
fn();  // still displays "5". Voila!

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

Применяя это к своему коду, вы должны изменить его следующим образом:

function createTimeoutHandler( elemArg, iDivStepsArg )
{
    return function() { setOpacity( elemArg, iDivStepsArg ); };
}

for (var i = 1; i <= steps; i++)
{
    console.log(i/steps + ', ' + (i/steps) * duration);
    setTimeout( createTimeoutHandler( elem, i/steps ), (i/steps) * duration);
}
3 голосов
/ 10 августа 2010

Ваш первый подход - оценка кода во время выполнения.Скорее всего, вы правы в том, почему это происходит с ошибкой (elem не входит в область действия кода).Использование любой формы eval()setTimeout(string, ...) - это форма eval()) является общей плохой идеей в Javascript, гораздо лучше создать функцию, как и во втором подходе.

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

Когда вы позже запускаете функцию, она использует эту ссылку для обращения к i изСфера fadeIn.Однако к тому времени, когда это произойдет, цикл закончится, и вы навсегда получите i, каким бы он ни был, когда этот цикл закончился.

Что вам нужно сделать, так это перестроить его так, чтобы вместомного setTimeouts одновременно (что неэффективно) вместо этого вы указываете своей функции обратного вызова setTimeout установить следующий тайм-аут (или вы можете использовать setInterval) и делать приращение, если ваши значения внутри этой функции обратного вызова.

...