【问题标题】:Calling functions with setTimeout()使用 setTimeout() 调用函数
【发布时间】:2023-03-26 22:15:01
【问题描述】:

简单地说……

为什么

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

完美运行,在指定延迟后调用函数,但是

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

同时调用函数playNote?

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

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

【问题讨论】:

标签: javascript settimeout


【解决方案1】:

您列出的第一种形式有效,因为它将评估delay 末尾的字符串。使用eval() 通常不是一个好主意,因此您应该避免这种情况。

第二种方法不起作用,因为您立即使用 the function call operator () 执行一个函数对象。最终发生的情况是,如果您使用 playNote(...) 形式,playNote 会立即执行,因此延迟结束时不会发生任何事情。

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

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

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

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

setTimeout(playNote, delay);

注意:

对于重复的事​​件,您可以使用 setInterval() 并且您可以将setInterval() 设置为一个变量,并使用该变量来停止与 clearInterval() 的间隔。

您说您在for 循环中使用setTimeout()。在许多情况下,最好在递归函数中使用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); }
}

【讨论】:

  • 严格来说,这不是递归,因为函数不是直接调用自己,它只是排队等待另一个调用自己稍后执行。至关重要的是,每次调用都会在下一次调用之前返回。
  • “递归”代码的一个大问题是,由于闭包的工作方式,每次对playNote 的连续调用都会在闭包链上添加一个条目,其长度将无限增加。就像无限递归一样,这是一个坏主意——你最终会耗尽内存!我已经编辑了答案以展示如何避免这种情况,同时保留一般的方法。
【解决方案2】:

试试这个。

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

【讨论】:

    【解决方案3】:

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

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

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

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

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

    但是,如果你在一个循环中,并且 currentaudio 或 noteTime 在循环中每次都不同,你就会遇到闭环问题:每次超时都会引用同一个变量,所以当它们'被调用时,您每次都会得到相同的值,即循环早些时候完成时留在变量中的值。

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

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

    但这现在有点难看。更好的是Function#bind,它将为您部分应用功能:

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

    (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)));
                };
            }
        };
    }
    

    【讨论】:

      【解决方案4】:

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

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

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

      setTimeout(playNote, delay);
      

      这不是真的。在给setTimeout 一个函数引用和延迟量之后,任何额外的参数都会被解析为被引用函数的参数。下面会比在函数中包装函数调用更好。

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

      始终查阅文档。

      也就是说,正如 Peter 指出的那样,如果您想改变每个 playNote() 之间的延迟,或者如果您希望每个 @ 之间有相同的延迟,可以考虑使用 setInterval() 987654328@.

      另外值得注意的是,如果你想将你的for循环的i解析成setTimeout(),你需要将它包装在一个函数中,详见here.

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          这可能有助于理解 javascript 何时执行代码,以及何时等待执行某事:

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

          • javascript 执行的第一件事是函数构造函数,并创建一个函数对象。您可以使用函数关键字语法 或 =&gt; 语法,并获得类似 (but not identical) 的结果。
          • 然后将刚刚创建的函数赋值给变量foo2
          • 此时没有运行任何其他函数:没有调用其他函数(baz 和 bar 都没有,没有查找值等。但是,已在函数内部检查了语法。
          • 如果您要将foo 或foo2 传递给setTimeout,那么在超时后,它将调用该函数,就像您执行foo() 一样。 (请注意,没有任何参数传递给 foo。这是因为默认情况下 setTimeout 不传递参数 although it can,但这些参数会在超时到期之前进行评估,而不是在超时到期时进行评估。)
          • 调用 foo 后,将评估默认参数。由于我们在没有传递参数的情况下调用 foo,因此评估了 bar 的默认值。 (如果我们传递一个参数,就不会发生这种情况)
          • 在评估 bar 的默认参数时,首先 javascript 会查找名为 baz 的变量。如果它找到一个,然后它会尝试将它作为一个函数调用。如果可行,它会将返回值保存到bar。
          • 现在评估函数的主体:
          • Javascript 查找变量bar,然后使用结果调用console.log。这不叫吧。然而,如果它被称为bar(),那么bar 将首先运行,然后bar() 的返回值将被传递给console.log。请注意,javascript 在调用该函数之前,甚至在它查找函数以查看它是否存在并且确实是一个函数之前, 获取它正在调用的函数的参数值。
          • Javascript 再次查找bar,然后尝试将其作为函数调用。如果可行,则返回值作为foo() 的结果

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2022-01-11
            • 1970-01-01
            • 1970-01-01
            • 2011-11-14
            • 2016-10-30
            相关资源
            最近更新 更多