使用setTimeout()调用函数

Ale*_*ang 38 javascript settimeout

简单的说...

为什么

setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);
Run Code Online (Sandbox Code Playgroud)

完美地工作,在指定的延迟后调用函数,但是

setTimeout(playNote(currentaudio.id,noteTime), delay);
Run Code Online (Sandbox Code Playgroud)

同时调用函数playNote?

(这些setTimeout()s在for循环中)

或者,如果我的解释太难阅读,这两个函数之间有什么区别?

Pet*_*tai 69

您列出的第一个表单有效,因为它将在结尾处评估字符串delay.使用eval()通常不是一个好主意,所以你应该避免这种情况.

第二种方法不起作用,因为您使用函数调用运算符()立即执行函数对象.最终发生的是,playNote如果您使用表单playNote(...),则会立即执行,因此在延迟结束时不会发生任何事情.

相反,您必须将匿名函数传递给setTimeout,因此正确的形式是:

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
Run Code Online (Sandbox Code Playgroud)

请注意,您正在传递setTimeout整个函数表达式,因此它将保留匿名函数并仅在延迟结束时执行它.

您也可以传递setTimeout引用,因为引用不会立即执行,但是您无法传递参数:

setTimeout(playNote, delay);
Run Code Online (Sandbox Code Playgroud)

注意:

对于您可以使用的重复事件setInterval(),您可以设置setInterval()为变量并使用该变量来停止间隔clearInterval().

你说你setTimeout()for循环中使用.在许多情况下,最好setTimeout()在递归函数中使用.这是因为在一个for循环中,变量中使用的变量setTimeout()不会像setTimeout()开始时那样变量,而是变量,因为它们是在触发函数时的延迟之后.

只需使用递归函数来回避整个问题.

使用递归来处理可变延迟时间:

  // Set original delay
var delay = 500;

  // Call the function for the first time, to begin the recursion.
playNote(xxx, yyy);

  // The recursive function
function playNote(theId, theTime)
{
    // Do whatever has to be done
    // ...

    // Have the function call itself again after a delay, if necessary
    //   you can modify the arguments that you use here. As an
    //   example I add 20 to theTime each time. You can also modify
    //   the delay. I add 1/2 a second to the delay each time as an example.
    //   You can use a condition to continue or stop the recursion

    delay += 500;

    if (condition)
    { setTimeout(function() { playNote(theID, theTime + 20) }, delay); }
}
Run Code Online (Sandbox Code Playgroud)

  • 详细而准确.. +1 (2认同)
  • 严格来说,这不是递归,因为该函数不是直接调用自身,而是只是排队对其自身进行另一个调用以在以后执行。至关重要的是,每个呼叫将在发起下一个呼叫之前返回。 (2认同)
  • 您的“递归”代码的一个大问题是,由于闭包的工作方式,每次连续调用`playNote`都会在闭锁链中添加一个条目,其长度会无限增加。就像无限递归一样,这是一个糟糕的想法-您最终将耗尽内存!我已经编辑了答案,以显示如何避免这种情况,同时总体上保留了该方法。 (2认同)

Dan*_*ite 7

试试这个.

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
Run Code Online (Sandbox Code Playgroud)


小智 6

我在这个网站上创建了一个帐户来评论 Peter Ajtai 的答案(目前投票最高),却发现你需要 50 名代表(无论是什么)来发表评论,所以我将其作为答案,因为它可能值得指出出了几件事。

在他的回答中,他陈述如下:

您还可以传递setTimeout引用,因为引用不会立即执行,但是您不能传递参数:

setTimeout(playNote, delay);
Run Code Online (Sandbox Code Playgroud)

这不是真的。给出setTimeout函数引用和延迟量后,任何附加参数都将被解析为引用函数的参数。下面的代码比将函数调用包装在函数中更好。

setTimeout(playNote, delay, currentaudio.id, noteTime)
Run Code Online (Sandbox Code Playgroud)

请始终查阅文档。

也就是说,正如 Peter 指出的那样,如果您想改变每个之间的延迟playNote(),或者setInterval()如果您希望每个之间有相同的延迟,请考虑使用递归函数playNote()

另外值得注意的是,如果您想将ifor 循环解析为 a setTimeout(),您需要将其包装在一个函数中,如此处详述


bob*_*nce 5

不要使用字符串超时。这是有效的eval,这是一件坏事。它之所以有效,是因为它正在将currentaudio.id和转换noteTime为自身的字符串表示并将其隐藏在代码中。这仅在这些值具有toString()生成 JavaScript 文字语法的 s时才有效,该语法将重新创建该值,这适用于Number但不适用于其他很多情况。

setTimeout(playNote(currentaudio.id, noteTime), delay);
Run Code Online (Sandbox Code Playgroud)

那是一个函数调用。playNote被立即调用,函数的返回结果(可能undefined)被传递给setTimeout(),而不是你想要的。

正如其他答案所提到的,您可以使用带有闭包的内联函数表达式来引用currentaudionoteTime

setTimeout(function() {
    playNote(currentaudio.id, noteTime);
}, delay);
Run Code Online (Sandbox Code Playgroud)

但是,如果你是在一个循环中和currentaudionoteTime周围循环每一次都是不同的,你已经得到了闭合回路问题:同样的变量将在每一个超时而被引用的,所以他们调用时,你会得到相同的value 每次循环较早结束时留在变量中的值。

您可以使用另一个闭包解决此问题,为循环的每次迭代获取变量值的副本:

setTimeout(function() {
    return function(currentaudio, noteTime) {
        playNote(currentaudio.id, noteTime);
    };
}(currentaudio, noteTime), delay);
Run Code Online (Sandbox Code Playgroud)

但这现在变得有点难看。更好的是Function#bind,它将为您部分应用一个函数:

setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);
Run Code Online (Sandbox Code Playgroud)

(window用于设置this函数内部的值,这是bind()您在这里不需要的功能。)

然而,这是 ECMAScript 第五版的功能,并非所有浏览器都支持。因此,如果您想使用它,您必须首先获得支持,例如:

// Make ECMA262-5 Function#bind work on older browsers
//
if (!('bind' in Function.prototype)) {
    Function.prototype.bind= function(owner) {
        var that= this;
        if (arguments.length<=1) {
            return function() {
                return that.apply(owner, arguments);
            };
        } else {
            var args= Array.prototype.slice.call(arguments, 1);
            return function() {
                return that.apply(owner, arguments.length===0? args : args.concat(Array.prototype.slice.call(arguments)));
            };
        }
    };
}
Run Code Online (Sandbox Code Playgroud)