Javascript:如何将不同的对象传递给循环中创建的setTimeout处理程序?

时间:2010-08-10 03:38:09

标签: javascript object settimeout

我正在尝试编写一些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未定义”错误。我认为这是因为当任何setOpacity调用实际运行时,初始的fadeIn函数已经完成,因此变量elem不再存在。

为了缓解这种情况,我尝试了另一种方法:

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

现在的麻烦是,当调用函数时,(i / steps)现在总是1.05而不是从0递增到1。

如何在正确提升不透明度的同时将相关对象传递给setOpacity?

2 个答案:

答案 0 :(得分:9)

你的“另一种方法”是正确的,这就是通常的做法。

至于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);
}

答案 1 :(得分:3)

您的第一种方法是在运行时评估代码。您很可能正确地知道它失败的原因(elem不在代码被评估的范围内)。使用任何形式的eval()(和setTimeout(string, ...)eval()的一种形式)在Javascript中是一个普遍的坏主意,在第二种方法中创建函数要好得多。

要理解为什么第二种方法失败,您需要了解范围,特别是闭包。当您创建该函数时,它会从i函数的范围中获取对fadeIn变量的引用。

当您稍后运行该函数时,它会使用该引用从i的范围返回fadeIn。然而,当这种情况发生时,循环就结束了,所以你永远只会让i成为循环结束时的任何东西。

你应该做的是重新设计它,以便不是一次创建多个setTimeout(这是低效的)而是告诉你的setTimeout回调函数设置下一个Timeout(或者你可以使用setInterval)并且如果不是你在回调函数中的值。