循环里的setTimeout

setTimeout函数的定义

Calls a function or executes a code snippet after a specified delay.
在指定的延迟后,执行代码/函数

setTimeout 函数接收两个参数,语法为:

1
timeoutID = setTimeout(func, delay);

第一个是将要执行的代码片段或者函数,第二个参数是延迟时间,单位是毫秒。例如,在5s后弹出提示:

1
2
3
setTimeout(function() {
alert("Times over");
}, 5000);

循环中的 setTimeout

1
2
3
4
5
for (var i = 0; i <= 3; i++) {
setTimeout(function logger() {
console.log(i);
}, 0);
}

循环中,在 0ms 后调用 logger 函数输出 i 的值。那么结果应该是:0 1 2 3

但实际代码的输出是:

1
4 4 4 4

了解 setTimeout

1
2
3
4
5
6
7
var start = new Date;
setTimeout(function logger(){
var end = new Date;
console.log('Time elapsed:', end - start, 'ms');
}, 500);
while (new Date - start < 1000) {};

执行代码片段后可以发现代码输出 Time elapsed: 10XXms。也就是说,logger函数事实上是在while 后执行的。

在 JavaScript 中,setTimeout 所传入的函数在线程空闲之前不会调用。在线程空闲后,并且满足 setTimeout 所设定的 delay 值后,函数才会被调用。在上面的代码中,logger函数 只有在 for 循环结束后才会被调用;而 for 循环结束时,i 的值为 4,故程序的输出为 4 4 4 4

setTimeout 中函数的 this

1
2
3
4
5
6
7
8
9
10
11
12
function setTimeoutTest() {
this.name = "Test function";
console.log(this);
for (var i = 0; i <= 3; i++) {
setTimeout(function logger() {
console.log(i);
console.log(this);
}, 0);
}
}
new setTimeoutTest();

上面的代码在控制台上输出两个变量:thisi

可以发现前后 this 的指向是不同的。在使用 new setTimeoutTest 之后,在 setTimeoutTest 内部 this 的值指向 setTimeoutTest 的一个实例,故第一个 this 指向 {name: "Test function"}logger函数 定义在 setTimeoutTest 函数内部,所以 logger函数 作用域链上保存着 i 以及 this 的值,而 logger函数是在全局作用域下调用的,此时作用域链上i的值依然是 4,但是 this 的值指向了 window。所以函数最终的输出为:

1
2
3
4
{name: "Test function"}
4 window
4 window
4 window

参考资料:

0%