简单地说...
为什么会...
setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);
工作得很完美,在指定延迟后调用函数,但是
setTimeout(playNote(currentaudio.id,noteTime), delay);
同时调用playNote函数吗?
(这些setTimeout()在一个for循环中)
或者,如果我的解释太难理解了,这两个函数有什么区别?
简单地说...
为什么会...
setTimeout('playNote('+currentaudio.id+', '+noteTime+')', delay);
工作得很完美,在指定延迟后调用函数,但是
setTimeout(playNote(currentaudio.id,noteTime), delay);
同时调用playNote函数吗?
(这些setTimeout()在一个for循环中)
或者,如果我的解释太难理解了,这两个函数有什么区别?
你列出的第一个形式有效,因为它将在delay
末尾计算一个字符串。通常不建议使用eval()
,因此应避免使用它。
第二种方法不起作用,因为你立即使用函数调用运算符()
执行了函数对象。使用形式playNote(...)
时,playNote
将立即执行,因此在延迟结束时什么也不会发生。
相反,你必须将匿名函数传递给setTimeout,因此正确的形式是:
setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
请注意,您正在传递 setTimeout
整个函数表达式,因此它将保留匿名函数并仅在延迟结束时执行它。
您也可以传递 setTimeout
引用,因为引用没有立即执行,但这样您就无法传递参数:
setTimeout(playNote, delay);
注意:
对于重复的事件,您可以使用setInterval()
,并且您可以将 setInterval()
赋值给一个变量,并使用该变量使用clearInterval()
停止时间间隔。
如果您在 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); }
}
试试这个。
setTimeout(function() { playNote(currentaudio.id,noteTime) }, delay);
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第五版的功能,不是所有浏览器都支持。因此,如果您想使用它,您必须先进行支持的hack,例如:
// 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)));
};
}
};
}
You can also pass
setTimeout
a reference, since a reference isn't executed immediately, but then you can't pass arguments:setTimeout(playNote, delay);
这是不正确的。在给setTimeout
函数传递函数引用和延迟时间后,任何附加参数都会被解析为引用函数的参数。下面的示例比将函数调用封装在函数中更好。
setTimeout(playNote, delay, currentaudio.id, noteTime)
始终查阅文档。
话虽如此,正如Peter所指出的,如果您想要在每个playNote()
之间变化延迟,递归函数是一个好主意,或者考虑使用setInterval()
如果您希望每个playNote()
之间有相同的延迟。
还值得注意的是,如果您想将for循环的i
解析为setTimeout()
,您需要将其包装在函数中,详见此处。
了解JavaScript执行代码的时机以及等待执行某些内容可能会有所帮助:
let foo2 = function foo(bar=baz()){ console.log(bar); return bar()}
=>
语法,您会得到类似的(但不完全相同)结果。foo2
baz
也没有调用bar
,没有查找任何值等)。但是,函数内部的语法已经被检查过了。foo
或foo2
传递给setTimeout
,则在超时后,它将调用该函数,就像您执行foo()
一样。(请注意,不会传递任何参数给foo
。这是因为setTimeout
默认不传递参数,虽然它可以, 但是这些参数在超时到期之前被计算,而不是在超时到期时被计算)bar
的默认值。(如果我们传递了一个参数,则不会发生这种情况)bar
的默认参数时,JavaScript首先查找名为baz
的变量。如果找到一个变量,则尝试将其作为函数调用。如果成功,则将返回值保存到bar
中。bar
,然后使用结果调用console.log。这不会调用bar。但是,如果它被称为bar()
,那么bar
将首先运行,然后将bar()
的返回值传递给console.log
。请注意,在调用函数之前,甚至在查找函数以查看其是否存在且确实是函数之前,JavaScript获取要调用的函数的参数值。bar
,然后尝试将其作为函数调用。如果成功,则该值将作为foo()
的结果返回()
),那么该函数也将立即执行。但是,您不需要调用函数。省略括号将允许您传递该函数并稍后调用它。然而,这样做的缺点是,您无法指定要调用函数时要使用的参数。另外,JavaScript 在调用函数或查找存储函数的变量之前会在函数括号内执行所有操作。
ctrl-k
即可实现此操作。 - Peter Ajtai