背景(您可能想跳过此内容)
我正在制作一个网络应用程序,可以在播放声音的同时激发英语音素的清晰度。它基于Interactive Sagittal Section by Daniel Currie Hall,可以找到第一次尝试here。
对于下一个版本,我希望每个音素都拥有自己的动画时序,这些时序在数组中定义,而数组又包含在对象变量中。
为了简化本文,我将时序数组变量从对象移动到函数中。
问题
我设置了一个for循环,我认为它会引用索引i和数组t来设置每个setTimeout的毫秒数。
function animateSam() {
var t = [0, 1000, 2000, 3000, 4000];
var key = "key_0";
for (var i = 0; i < t.length; i++) {
setTimeout(function() {
console.log(i);
key = "key_" + i.toString();
console.log(key);
//do stuff here
}, t[i]);
}
}
animateSam()
然而,似乎毫秒是由函数到达堆栈顶部时发生的任何事情设置的。
问题:有没有可靠的方法来设置数组的毫秒数?
答案 0 :(得分:3)
for在setTimeout函数完成之前结束,所以你必须在闭包内设置超时:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
for (var i = 0; i < t.length; i++) {
(function(index) {
setTimeout(function() {
alert (index);
key = "key_" + index.toString();
alert (key);
//do stuff here
}, t[index]);
})(i);
}
}
在这里您可以解释为什么会发生这种情况: https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b
答案 1 :(得分:3)
for
循环将在第一个setTimeout
被触发之前循环所有元素,因为它具有异步性质。当您的循环运行时,i
将等于5
。因此,您可以获得相同的输出五次。
这确保了函数被包含在内。
[0, 1000, 2000, 3000, 4000].forEach((t, i) => {
setTimeout(function() {
console.log(i);
console.log(`key_${i}`);
//do stuff here
}, t)
});
旁注:我会建议你不在工作/调试时使用alert
,因为它真的很让人困惑和讨厌。最好是使用简单的console.log
。
关于代码的更多说明:
.forEach
接收要在每个元素上运行的回调函数作为主要参数。这个回调本身可以带两个参数(在我们之前的代码t
中是当前元素的值,i
是当前元素在数组中的索引):
Array.forEach(function(value, index) {
});
但您可以使用arrow function syntax,而不是使用function(e,i) { ... }
定义回调,而不是使用(e,i) => { ... }
定义回调。就这样!然后代码看起来像:
Array.forEach((value,index) => {
});
此语法是定义回调的较短方法。有some differences though。
答案 2 :(得分:1)
我建议使用函数闭包,如下所示:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
var handleAnimation = function (idx) {
return function() {
alert(idx);
key = "key_" + idx.toString();
alert(key);
//do stuff here
};
}
for (var i = 0; i < t.length; i++) {
setTimeout(handleAnimation(i), t[i]);
}
}
在本例中,您将实际函数包装在一个包装函数中,该函数捕获变量并传递值。