setTimeout 和匿名函数问题

Posted

技术标签:

【中文标题】setTimeout 和匿名函数问题【英文标题】:setTimeout and anonymous function problem 【发布时间】:2011-01-11 09:52:58 【问题描述】:

这是我的代码,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执行之前,i将达到endOpacity(来自for循环的最后一个值)

不幸的是 javascript 只有函数作用域,所以如果你在循环中创建变量并分配一个新的实际值,它就不会起作用,因为只要函数内有一些var,这些变量就会在当时创建函数执行(默认为undefined)。创建新作用域的唯一(简单)方法是创建一个函数(可能是匿名的)并在其中创建新变量(参数也是变量)。

【讨论】:

+1 - 我也开始使用匿名函数。再看一遍,我猜你的更优雅。 你能用 (i) 解释一下吗?【参考方案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++;
        
               

【讨论】:

你测试过这个吗? var opacity 仍与 i 在同一范围内,因此据我所知,它应该仍然中断。【参考方案3】:

Kobi 在这个问题上的想法是正确的。不过,我建议您改用间隔。

这是一个示例:(您的 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 检查 "recentlyOut" 类看它是否需要向上滑动。

代码不言自明。

$(".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);

    );

【讨论】:

以上是关于setTimeout 和匿名函数问题的主要内容,如果未能解决你的问题,请参考以下文章

setTimeout 的用法

js中的匿名函数

javascript之小积累-匿名函数表达式的最佳实践

箭头函数、普通函数和this的区别

无法在具有匿名功能的函数内使用“this”

函数节流