Javascript 使用 setTimeout() 调用函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3800512/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 06:12:48  来源:igfitidea点击:

Calling functions with setTimeout()

javascriptsettimeout

提问by Alex Hwang

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?

同时调用函数 playNote ?

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

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

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

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

回答by Peter Ajtai

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.

您列出的第一种形式有效,因为它将在delay. 使用eval()通常不是一个好主意,所以你应该避免这种情况。

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

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

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

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

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

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

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

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

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

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().

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

You say you use setTimeout()in a forloop. In many situations, it is better to use setTimeout()in a recursive function. This is because in a forloop, 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.

你说你setTimeout()for循环中使用。在许多情况下,最好setTimeout()在递归函数中使用。这是因为在for循环中, 中使用的变量setTimeout()将不是setTimeout()开始时的变量,而是函数被触发时延迟后的变量。

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

回答by Daniel A. White

Try this.

尝试这个。

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

回答by bobince

Don't use string-timeouts. It's effective an eval, which is a Bad Thing. It works because it's converting currentaudio.idand noteTimeto 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 Numberbut not for much else.

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

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

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

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

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

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

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

However, if you're in a loop and currentaudioor noteTimeis 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.

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

You can work around this with anotherclosure, 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:

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

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

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

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

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.:

然而,这是 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)));
            };
        }
    };
}

回答by Runny-Yolk

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.

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

In his answer, he states the following:

在他的回答中,他陈述了以下内容:

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

setTimeout(playNote, delay);

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

setTimeout(playNote, delay);

This isn't true. After giving setTimeouta 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函数引用和延迟量之后,任何附加参数都被解析为被引用函数的参数。下面的内容比将函数调用包装在一个函数中要好。

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().

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

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

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

回答by Garrett Motzner

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

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

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

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 orthe =>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 baznor bar, no values looked up, etc. However, the syntax has been checked inside the function.
  • If you were to pass fooor foo2to setTimeoutthen 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 setTimeoutdoesn'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 baris 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 barand then calls console.log with the result. This does not call bar. However, if it was instead called as bar(), then barwould run first, and then the return value of bar()would be passed to console.loginstead. Notice that javascript gets the values of the arguments to a function it is calling beforeit 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()
  • javascript 执行的第一件事是函数构造函数,并创建一个函数对象。您可以使用function关键字语法=>语法,你会得到类似(但不相同)的结果。
  • 然后将刚刚创建的函数分配给变量 foo2
  • 在这一点上,没有其他任何东西被运行:没有调用其他函数(也baz没有bar,也没有查找值等。但是,已经在函数内部检查了语法。
  • 如果您要在超时后通过foofoo2setTimeoutthen,它将调用该函数,就像您所做的一样foo()。(请注意,没有 args 传递给foo。这是因为setTimeout默认情况下不会传递参数,尽管它可以,但这些参数会在超时到期之前进行评估,而不是在超时到期时进行评估。)
  • 调用 foo 后,将评估默认参数。由于我们在没有传递参数的情况下调用了 foo,因此bar评估了默认值。(如果我们传递参数就不会发生这种情况)
  • 在评估 的默认参数时bar,首先 javascript 查找名为 的变量baz。如果它找到一个,它就会尝试将它作为一个函数来调用。如果可行,它将返回值保存到bar.
  • 现在评估函数的主体:
  • Javascript 查找变量bar,然后使用结果调用 console.log。这不叫吧。但是,如果它被称为 as bar(),则将bar首先运行,然后将其返回值bar()传递给console.log。请注意,javascript调用函数之前获取它正在调用的函数的参数值,甚至在它查找函数以查看它是否存在并且确实是一个函数之前。
  • Javascript 再次查找bar,然后尝试将其作为函数调用。如果可行,则该值将作为结果返回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 beforeit calls the function or looks up the variable the function is stored in.

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

回答by dgnorton

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

由于第二个你告诉它来调用playNote功能第一,然后从它传递返回值的setTimeout。