使用 setTimeout() 调用函数

Calling functions with setTimeout()

提问人:quantum rookie 提问时间:9/27/2010 最后编辑:Peter Ajtaiquantum rookie 更新时间:10/1/2022 访问量:110599

问:

简单地说......

为什么

setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);

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

setTimeout(playNote(currentaudio.id,noteTime), delay);

同时调用函数 playNote?

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

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

JavaScript 设置超时

评论

2赞 Cfreak 9/27/2010
不要在循环中使用 setTimeout。请改用 setInterval()。它将在延迟间隔内一遍又一遍地调用您指定的函数,直到您告诉它停止。
0赞 Peter Ajtai 9/27/2010
一行前面的 4 个空格将其格式化为代码。选择一个块并按下以执行此操作。ctr-k
0赞 jcolebrand 9/27/2010
请注意@Cfreak提供的建议...除非必要,否则不要手动循环。引擎有一种处理重复事件的方法,你不必自己重新发明它(如果你做错了,你可以诱导出可爱的“这个脚本已停止响应”)
1赞 quantum rookie 9/27/2010
哈哈谢谢大家,但我必须使用 setTimeout(),因为每次的延迟都不同,而不是恒定:)
0赞 Sebastian Simon 9/30/2021
相关:为什么使用 setTimeout 时该方法会立即执行?,如何将参数传递给 setTimeout() 回调?,setTimeout 函数中的第三个参数是什么?

答:

8赞 Daniel A. White 9/27/2010 #1

试试这个。

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
2赞 dgnorton 9/27/2010 #2

因为第二个你告诉它调用 playNote 函数,然后将返回值从它传递给 setTimeout。

79赞 Peter Ajtai 9/27/2010 #3

您列出的第一种形式有效,因为它将计算 末尾的字符串。使用通常不是一个好主意,所以你应该避免这种情况。delayeval()

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

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

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);

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

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

setTimeout(playNote, delay);

注意:

对于重复事件,您可以使用 setInterval(),也可以设置为变量,然后通过 clearInterval( 使用该变量来停止间隔。setInterval()

你说你循环使用。在许多情况下,最好在递归函数中使用。这是因为在循环中,中使用的变量将不是开始时的变量,而是函数触发时延迟后的变量。setTimeout()forsetTimeout()forsetTimeout()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); }
}

评论

3赞 Doin 5/13/2016
严格来说,这不是递归,因为函数不是直接调用自己,它只是排队等待对自身的另一个调用,以便稍后执行。至关重要的是,每个调用都会在启动下一个调用之前返回。
3赞 Doin 5/13/2016
“递归”代码的一个大问题是,由于闭包的工作方式,每次连续调用 to 都会在闭包链上添加一个条目,其长度将无限增加。就像无限递归一样,这是一个坏主意——你最终会耗尽内存!我编辑了答案,以展示如何避免这种情况,同时保留该方法。playNote
7赞 bobince 9/27/2010 #4

不要使用字符串超时。这是有效的,这是一件坏事。它之所以有效,是因为它正在转换和转换为自己的字符串表示形式并将其隐藏在代码中。这只有在这些值具有生成 JavaScript 文本语法的 s 时才有效,该语法将重新创建该值,这对于其他值是正确的,但对于其他很多值来说都不是。evalcurrentaudio.idnoteTimetoString()Number

setTimeout(playNote(currentaudio.id, noteTime), delay);

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

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

setTimeout(function() {
    playNote(currentaudio.id, noteTime);
}, delay);

但是,如果你处于循环中,并且每次循环都不同,你就遇到了闭合循环问题:每次超时都会引用相同的变量,所以当它们被调用时,你每次都会得到相同的值,即循环之前完成时变量中留下的值。currentaudionoteTime

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

setTimeout(function() {
    return function(currentaudio, noteTime) {
        playNote(currentaudio.id, noteTime);
    };
}(currentaudio, noteTime), delay);

但现在这有点丑陋了。更好的是 ,它将部分应用一个函数:Function#bind

setTimeout(playNote.bind(window, currentaudio.id, noteTime), delay);

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

但是,这是 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)));
            };
        }
    };
}
6赞 Runny-Yolk 7/27/2017 #5

我在这个网站上创建了一个帐户来评论 Peter Ajtai 的答案(目前得票最高),结果发现您需要 50 个代表(不管是什么)才能发表评论,所以我会把它作为一个答案,因为它可能值得指出几件事。

在他的回答中,他指出:

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

setTimeout(playNote, delay);

事实并非如此。在给出函数引用和延迟量后,任何其他参数都将被解析为引用函数的参数。下面比将函数调用包装在函数中要好。setTimeout

setTimeout(playNote, delay, currentaudio.id, noteTime)

始终查阅文档。

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

另外值得注意的是,如果你想将 for 循环解析为 ,你需要将其包装在一个函数中,详见此处。isetTimeout()

3赞 Garrett Motzner 1/19/2019 #6

了解 javascript 何时执行代码以及何时等待执行某些内容可能会有所帮助:

let foo2 = function foo(bar=baz()){ console.log(bar); return bar()}

  • javascript 执行的第一件事是函数构造函数,并创建一个函数对象。您可以使用函数关键字语法语法,您将获得相似(但不完全相同)的结果。=>
  • 然后将刚刚创建的函数分配给变量foo2
  • 此时没有运行任何其他函数:没有调用其他函数(既不是也不是,没有查找值等。但是,语法已在函数内部进行了检查。bazbar
  • 如果要在超时后传递或 to,它将调用该函数,就像您调用函数一样。(请注意,没有将任何参数传递给 。这是因为默认情况下不会传递参数,尽管它可以,但这些参数在超时到期之前进行评估,而不是在超时到期时进行评估。foofoo2setTimeoutfoo()foosetTimeout
  • 调用 foo 后,将计算默认参数。由于我们在未传递参数的情况下调用了 foo,因此将计算默认值。(如果我们通过一个论点,这就不会发生)bar
  • 在计算 的默认参数时,首先 javascript 会查找一个名为 的变量。如果它找到一个,它就会尝试将其作为函数调用。如果可行,则将返回值保存到 。barbazbar
  • 现在计算函数的主体:
  • Javascript 查找变量,然后使用结果调用 console.log。这不调用栏。但是,如果它被调用为 ,则将首先运行,然后将返回值传递给 。请注意,javascript 在调用函数之前,甚至在查找函数以查看它是否存在并且确实是一个函数之前,都会获取它正在调用的函数的参数值。barbar()barbar()console.log
  • Javascript 再次查找 ,然后尝试将其作为函数调用。如果可行,则返回该值作为barfoo()

因此,函数体和默认参数不会立即调用,但其他一切都会立即调用。同样,如果您执行函数调用(即 ),则该函数也会立即执行。但是,不需要调用函数。省略括号将允许您传递该函数并在以后调用它。但是,这样做的缺点是,您无法指定要调用函数的参数。此外,javascript 在调用函数或查找存储函数的变量之前,会在函数括号内执行所有操作。()