使用 setTimeout() 调用函数

发布于 2024-09-25 04:21:51 字数 325 浏览 6 评论 0原文

简单地说...

为什么

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

工作正常,在指定的延迟后调用该函数,但

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

同时调用函数 playNote ?

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

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

Simply put...

why does

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

work perfectly, calling the function after the the specified delay, but

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

calls the function playNote all at the same time?

(these setTimeout()s are in a for loop)

or, if my explanation is too hard to read, what is the difference between the two functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

小草泠泠 2024-10-02 04:21:51

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

第二种方法不起作用,因为您立即使用 执行函数对象函数调用运算符()。最终发生的情况是,如果您使用 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); }
}

The first form that you list works, since it will evaluate a string at the end of delay. Using eval() is generally not a good idea, so you should avoid this.

The second method doesn't work, since you immediately execute a function object with the function call operator (). What ends up happening is that playNote is executed immediately if you use the form playNote(...), so nothing will happen at the end of the delay.

Instead, you have to pass an anonymous function to setTimeout, so the correct form is:

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

Note that you are passing setTimeout an entire function expression, so it will hold on to the anonymous function and only execute it at the end of the delay.

You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:

setTimeout(playNote, delay);

Note:

For repeated events you can use setInterval() and you can set setInterval() to a variable and use the variable to stop the interval with clearInterval().

You say you use setTimeout() in a for loop. In many situations, it is better to use setTimeout() in a recursive function. This is because in a for loop, the variables used in the setTimeout() will not be the variables as they were when setTimeout() began, but the variables as they are after the delay when the function is fired.

Just use a recursive function to sidestep this entire problem.

Using recursion to deal with variable delay times:

  // 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); }
}
心奴独伤 2024-10-02 04:21:51

试试这个。

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

Try this.

setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
丢了幸福的猪 2024-10-02 04:21:51

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

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

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

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

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

但是,如果您处于循环中并且 currentaudio< /code> 或 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)));
            };
        }
    };
}

Don't use string-timeouts. It's effective an eval, which is a Bad Thing. It works because it's converting currentaudio.id and noteTime to the string representations of themselves and hiding it in the code. This only works as long as those values have toString()s that generate JavaScript literal syntax that will recreate the value, which is true for Number but not for much else.

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

that's a function call. playNote is called immediately and the returned result of the function (probably undefined) is passed to setTimeout(), not what you want.

As other answers mention, you can use an inline function expression with a closure to reference currentaudio and noteTime:

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

However, if you're in a loop and currentaudio or noteTime is different each time around the loop, you've got the Closure Loop Problem: the same variable will be referenced in every timeout, so when they're called you'll get the same value each time, the value that was left in the variable when the loop finished earlier.

You can work around this with another closure, taking a copy of the variable's value for each iteration of the loop:

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

but this is getting a bit ugly now. Better is Function#bind, which will partially-apply a function for you:

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

(window is for setting the value of this inside the function, which is a feature of bind() you don't need here.)

However this is an ECMAScript Fifth Edition feature which not all browsers support yet. So if you want to use it you have to first hack in support, eg.:

// 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)));
            };
        }
    };
}
我不会写诗 2024-10-02 04:21:51

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

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

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

setTimeout(playNote, 延迟);

这是不正确的。为 setTimeout 提供函数引用和延迟量后,任何其他参数都会被解析为引用函数的参数。下面的代码比将函数调用包装在函数中更好。

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

请始终查阅文档。

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

另外值得注意的是,如果您想将 for 循环的 i 解析为 setTimeout(),则需要将其包装在一个函数中,详细信息 此处。

I literally created an account on this site to comment on Peter Ajtai's answer (currently highest voted), only to discover that you require 50 rep (whatever that is) to comment, so I'll do it as an answer since it's probably worth pointing out a couple things.

In his answer, he states the following:

You can also pass setTimeout a reference, since a reference isn't executed immediately, but then you can't pass arguments:

setTimeout(playNote, delay);

This isn't true. After giving setTimeout a function reference and delay amount, any additional arguments are parsed as arguments for the referenced function. The below would be better than wrapping a function call in a function.

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

Always consult the docs.

That said, as Peter points out, a recursive function would be a good idea if you want to vary the delay between each playNote(), or consider using setInterval() if you want there to be the same delay between each playNote().

Also worth noting that if you want to parse the i of your for loop into a setTimeout(), you need to wrap it in a function, as detailed here.

城歌 2024-10-02 04:21:51

它可能有助于理解 javascript 何时执行代码,以及何时等待执行某些内容:

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

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

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

It may help to understand when javascript executes code, and when it waits to execute something:

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

  • The first thing javascript executes is the function constructor, and creates a function object. You can use either the function keyword syntax or the => syntax, and you get similar (but not identical) results.
  • The function just created is then assigned to the variable foo2
  • At this point nothing else has been run: no other functions called (neither baz nor bar, no values looked up, etc. However, the syntax has been checked inside the function.
  • If you were to pass foo or foo2 to setTimeout then after the timeout, it would call the function, the same as if you did foo(). (notice that no args are passed to foo. This is because setTimeout doesn't by default pass arguments, although it can, but those arguments get evaluated before the timeout expires, not when it expires.)
  • After foo is called, default arguments are evaluated. Since we called foo without passing arguments, the default for bar is evaluated. (This would not have happened if we passed an argument)
  • While evaluating the default argument for bar, first javascript looks for a variable named baz. If it finds one, it then tries to call it as a function. If that works, it saves the return value to bar.
  • Now the main body of the function is evaluated:
  • Javascript looks up the variable bar and then calls console.log with the result. This does not call bar. However, if it was instead called as bar(), then bar would run first, and then the return value of bar() would be passed to console.log instead. Notice that javascript gets the values of the arguments to a function it is calling before it calls the function, and even before it looks up the function to see if it exists and is indeed a function.
  • Javascript again looks up bar, and then it tries to call it as a function. If that works, the value is returned as the result of foo()

So, function bodies and default arguments are not called immediately, but everything else is. Similarly, if you do a function call (i.e. ()), then that function is executed immediately as well. However, you aren't required to call a function. Leaving off the parentheses will allow you to pass that function around and call it later. The downside of that, though, is that you can't specify the arguments you want the function to be called with. Also, javascript does everything inside the function parentheses before it calls the function or looks up the variable the function is stored in.

邮友 2024-10-02 04:21:51

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

Because the second one you're telling it to call the playNote function first and then pass the return value from it to setTimeout.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文