setTimeout()方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。
let timer1 = setTimeout(function[, delay, arg1, arg2, ...]);
let timer2 = setTimeout(function[, delay]);
let timer3 = setTimeout(code[, delay]);
delay作为可选参数,意义是延迟的毫秒数,默认值为0,函数的调用会在该延迟之后发生。如果省略该参数,意味着“马上”执行,或者尽快执行。不管是哪种情况,实际的延迟时间可能会比期待的(delay毫秒数) 值长。
比如下面的代码:
let i = 0
for(i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
/*
6
6
6
6
6
6
*/
// 打印出6个6的原因是console.log(i)在for循环结束后才被执行
有很多因素会导致setTimeout的回调函数执行比设定的预期值更久,MDN上列举了一些常见的原因。
这里的原因应该是:
定时器有可能因为当前页面(或者操作系统/浏览器本身)被其他任务占用导致延时。 需要被强调是, 直到调用 setTimeout()的主线程执行完其他任务之后,回调函数和代码段才能被执行。
...
尽管setTimeout 以0ms的延迟来调用函数,但这个任务已经被放入了队列中并且等待下一次执行;并不是立即执行;队列中的等待函数被调用之前,当前代码必须全部运行完毕,因此这里运行结果并非预想的那样。
将上面的代码稍微改动一下:
for(let i = 0; i<6; i++){
setTimeout(()=>{
console.log(i)
},0)
}
/*
0
1
2
3
4
5
*/
在方应航的知乎专栏中,给出了这样的解释:
- for( let i = 0; i< 5; i++) 这句话的圆括号之间,有一个隐藏的作用域
- for( let i = 0; i< 5; i++) { 循环体 } 在每次执行循环体之前,JS 引擎会把 i 在循环体的上下文中重新声明及初始化一次。
- 其他细节就不说了,太细碎了 也就是说上面的代码段2可以近似近似近似地理解为
// 代码段3 var liList = document.querySelectorAll('li') // 共5个li for( let i=0; i<liList.length; i++){ let i = 隐藏作用域中的i // 看这里看这里看这里 liList[i].onclick = function(){ console.log(i) } }那样的话,5 次循环,就会有 5 个不同的 i,console.log 出来的 i 当然也是不同的值。
再加上隐藏作用域里的 i,一共有 6 个 i。
这就是 MDN 加那句 let j = i 的原因:方便新人理解。
总得来说就是 let/const 在与 for 一起用时,会有一个 perIterationBindings 的概念(一种语法糖)。
MDN在文档中提供了这篇文章,作者 David Baron 给出了一种比setTimeout更短延迟的方案:
// Only add setZeroTimeout to the window object, and hide everything
// else in a closure.
(function() {
var timeouts = [];
var messageName = "zero-timeout-message";
// Like setTimeout, but only takes a function argument. There's
// no time argument (always zero) and no arguments (you have to
// use a closure).
function setZeroTimeout(fn) {
timeouts.push(fn);
window.postMessage(messageName, "*");
}
function handleMessage(event) {
if (event.source == window && event.data == messageName) {
event.stopPropagation();
if (timeouts.length > 0) {
var fn = timeouts.shift();
fn();
}
}
}
window.addEventListener("message", handleMessage, true);
// Add the one thing we want added to the window object.
window.setZeroTimeout = setZeroTimeout;
})();
但这并不能让我们一开始的代码输出012345。
如果setTimeout延时太久了,那么我们让for循环更慢一些呢?比如:
sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
! async function(){
let i = 0
for(i = 0; i<6; await sleep(0),i++){
setTimeout(()=>{
console.log(i)
})
}
}()
/*
0
1
2
3
4
5
*/
虽然得到了想要的结果,但并不知道这有什么卵用......
参考自: