话很少说先看代码来引出今天的问题javascript
//下面两个定时器的输出的前后顺序是啥呢?
setTimeout(function(){
console.log("200")
},200)
//不了解ES6的朋友,把let 当成var 就好
for(let i = 0 ; i < 1000 ; i++){
console.log('---');
}
setTimeout(function(){
console.log('0')
//实际不可能会是0ms,定时器有一个最低的延时为4ms,形成这个的缘由,我相信聪明的你,
//确定能在下面的世界轮循机制中找到答案(定时器触发线程和主线程的取出,会有必定的执行时间)
},0)
//而下面两个定时器的输出结果又是啥呢?
setTimeout(function(){
console.log("200")
},200)
for(let i= 0; i < 5000 ; i++){
console.log("---");
}
setTimeout(function(){
console.log("0")
},0)
复制代码
//上面两个的答案分别是 0 200; 200 0 复制代码
。那么问题来了,第二个定时的delay(延迟时间,如下都用这个单词表示了)明明是 0ms(实际大约4ms,代码中解释了,下面再也不作解释)。第一个定时器的delay 是 200ms,为啥第一个代码正常输出,而第二个代码确实 delay为200ms 的先输出?
前端
如今带着咱们的问题来看看js的事件轮循(Event Loop)机制:java
再来看看同步任务具体执行的过程ajax
function foo(){
function bar(){
console.log("bar"); }
bar();
console.log("foo");
}
foo();复制代码
咱们来具体看看上面的执行过程json
咱们再来深刻了解下执行栈:浏览器
上面代码咱们只套了一层函数,若是套多层函数,或者有多个bar的同级函数是有区别的。bash
多层嵌套很简单,就按照上面的流程依次内推就行了,网络
同级函数则是是重复 3,4,5的步骤。bar执行完毕,弹出栈,bar后面的代码继续执行碰到函数执行则走3,4,5,步骤。dom
4.异步任务具体的执行过程异步
$.ajax({
url: ‘localhost:/js/demo.json’,
data: {},
success: function (data) {
console.log(data);
}
});
console.log(‘run’);
复制代码
5.换一张图继续理解
对2 作一点补充:
细心的朋友已经发行,我在上面写 主线程的时候()里面写了一个调用栈。没错 执行栈其实至关于js主线程。个人我的理解,js单线程执行是,遇到同步的代码,从上到下依次(预编译的问题另说),遇到异步的代码就一脚踢开,让该管异步代码的去管理(参考第一点浏览器常驻线程)。等同步代码执行完毕以后,再去看看Event Queue(任务队列)里面看看有没有,能够执行的代码(回调,定时器,事件),有就拿过来执行,没有就一会再来看看(这个事件特别短,也多是有专门的触发机制,总的就是 只有执行栈为空,Event Queue里面有任务就会立刻拿来执行)
好了,说到这里,就能够回头来看看咱们最开始抛出的问题:
对上面代码的分析:
由上面的文字能够分析出,只要for循环的执行时间超过了200ms,第一个定时器就先进入Event Queue中(任务队列,先进先出,后进后出。先进去的就先执行),第二个定时器是在第一个定时器已经进入了Event Queue 以后再触发的,无论他的delay多小也只有后输出。
而for循环的执行时间没有超过200ms时(低于先触发的定时器的delay),for循环执行完毕后,他还在面壁思过的数数,js主线程继续往下走,触发了第二个定时器,依旧一脚踢开,去面壁思过数数,这个时候,只要谁先数完,谁就先进入Event Queue 就先执行 。 上面代码的状况是 delay 为0ms 的先数完,因此先执行,delay为200ms后进入Event Queue 后执行。
你觉得这样就完了吗?若是是这样敢说深度剖析定时器?看代码
//表示执行次数的变量
let count = 0; /
/开始时间,用来定时的,记录执行的间隔时间
// + 为一元 '+' 号运算符,将其操做数隐式转换成数字
let starTime = +new Date();
function sleep (num){
for(let i = 0 ;i < num ; i++){
console.log(i);
}
}
setInterval(function(){
count++;
console.log(+new Date() - starTime , count);
starTime = +new Date();
},1000)
sleep(20000);复制代码
先上执行结果
上面的执行结果除了第二次的都很好解释。第一次执行,时间这么多的缘由是,运行for循环完了以后才能执行定一次的定时器,3以后的就趋于稳定 大概等于delay。
先抛出问题:
首先主线程一直在运行的时候,setInterval是每到一个delay就往Event Queue推出一个执行函数吗?若是是这样的话,如图所示第一次执行被阻塞的时候为3000 + ,因此能往Evnet Queue里面注册三个定时器,为啥只有第二次的执行间隔时间发生比较大的差距,第三次之后就正常了? 为何 第一次和第二次执行的间隔时间相加总约等于delay的倍数,这是巧合仍是必然?
回答问题:
咱们先定义一些参数,好方便如下的解释:
fn1 为定时器的第一次 , fn 2 为定时器的第二次 , fn3 为定时器 第三次和之后的无限次
关于上面的第一个问题很容易回答, setInterval 确定不是没到一个delay就往Event Queue 推送一个执行函数 ,若是是的话如上代码就会有三个执行函数在任务队列里面了,当主线程执行完毕后,去Event Queue拿函数回去执行会很是快,不可能会出现,fn2,fn3执行间隔这么大。 其实第三个问题才是解题的关键,是仔细想想,什么状况下才能出现这种相加为倍数的状况(好吧,其实怎么想,我也说不清楚)。在试验的过程当中,甚至出现过fn1 的执行间隔为3950 ,fn2的执行间隔为49的状况,当时确实给我形成了很大的悟道,后面经过不断的实验,加询问最终得出告终论
解决:出现这个事情的缘由是,Event Queue 里面只能存在同一个定时器的一次事件,也就是说在定时器第一次被拿到主线程取走以前,第二次并不会进入Event Queue 。会依旧再Event Table 里面等待。这个等待并非盲目的等待,在每个delay周期都看看Event Queue 里面 上一次 的进去的定时器(fn1) 被主线程取走没有,当取走后,就会在当前delay周期完的时候,把这一次的定时器(fn2)推入 Event Queue ,而这个时候主线程正好没有任务正在执行,主线程就会马上把此次的定时器放入到主线程执行,就形成了,定时器第一次执行和第二次执行的间隔时间相加总等于delay的倍数。 fn3以后的就属于正常状况了,当主线程没有任务,Event Queue 中没有定时器时,就每隔delay执行一次。
第一次写掘金文章(也是第一次写文章),清辩证看待,其中的一些错别字和错误。若是对你有帮助,别忘了点个赞哟。
最后打个广告,本人男,22岁,在校大四学习。坐标成都,但愿能找个前端的正式岗或者实习岗工做。若是有招人或者内推的大佬,能够留言细聊哟。