setTimeout运行原理
先观察以下代码
var start = new Date;
setTimeout(function(){
console.log('时间流逝了:'+(new Date - start)+'毫秒');
}, 200);
while (new Date - start < 1000) {}
console.log(1);
function doSoming(){
setTimeout(function(){
console.log('时间又流逝了:'+(new Date - start)+'毫秒');
},10);
}
doSoming();
while (new Date - start < 2000) {}
console.log(2);
上述代码的结果是:
以上结果表明几个现象:
1.定时任务在非定时任务后执行;
2.setTimeout中的delay参数不一定越小越早执行;
分析第一点看出来,很明显,settimeout是在其他非延时任务后执行的,简单的原理就是:
在现有浏览器环境中,Javascript执行引擎是单线程的,主线程的语句和方法,会阻塞定时任务的运行,在Javascript执行引擎之外,存在一个任务队列,当在代码中调用setTimeout()方法时,注册的延时方法会挂到浏览器内核其他模块处理,当延时方法到达触发条件,即到达设置的延时时间时,该模块再将要执行的方法添加至该模块的任务队列中。这一过程与执行引擎主线程独立,执行引擎在主线程方法执行完毕,到达空闲状态时,才会从该模块的任务队列中顺序提取任务来执行,这期间的时间,可能大于注册任务时设置的延时时间;
浏览器在空闲状态下,会不断的尝试从模块的任务队列中提取任务,这称为事件循环模型;
分析第二点,原因是什么呢?
因为while函数内部的非定时任务阻塞了约1000ms,第一个定时任务已经在约200ms时被放进了任务队列中,此时第二个定时任务还没有运行到。如果while函数阻塞的时间+第二个定时任务delay的时间,那么结果就会变成
image.png
setTimeout循环内的闭包陷阱
for(var i =0; i <10; i++){
setTimeout(function(){
console.log(i);
},1000);
}
上述代码的输出结果是什么?
输出十次10,这是因为匿名函数闭包的作用,在循环结束后再执行定时任务时,i已经递增到10了,原来的值被改变了。
为了得到理想的结果,有以下解决方案
- 自执行匿名函数
每次循环时,使用自执行匿名函数进行对i的拷贝,再执行定时任务时就会访问当时传入的参数。
for(var i =0; i <10; i++){
(function(e){
setTimeout(function(){
console.log(e);
},1000);
})(i);
}
- 定时器内自执行函数
for(var i =0; i <10; i++){
setTimeout((function(e){
console.log(e);
})(i),1000)
}
- es6 let
使用let关键字使得for循环变成块级作用域
for(let i =0; i <10; i++){
setTimeout(function(){
console.log(i);
},1000);
}
参考链接: 你真的知道setTimeout是如何运行的吗








网友评论