我正在尝试编写一些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?
答案 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)并且如果不是你在回调函数中的值。