前言
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
参考视频:【中文字幕】Philip Roberts:到底什么是Event Loop呢?(JSConf EU 2014)_哔哩哔哩_bilibili
参考文章:
The event loop - JavaScript | MDN (mozilla.org)
JavaScript 运行机制详解:再谈Event Loop - 阮一峰的网络日志 (ruanyifeng.com)
理论模型
首先我们先来看一下javascript的理论模型(当时视频中介绍的是:chrome的V8的引擎):
这边我们可以看到,一共有三给区域
-
Stack:这个是,当函数被调用的时候,那么我们这个函数的帧Frame就会进入这个栈,并且这个Frame里面是会包括这个函数的很多内容(参数等),当我们这个函数执行完成以后(return)以后,我们才会将这个Frame释放掉 -
Queue:这个就是用于存储Message的地方,每一个Message都会关联着一个函数意思就是:当我们这个
Message被处理,那么首先Message从Queue里面shift出去,然后,Message会引来一个函数,那么这个函数unshift进入,然后执行,然后执行完,pop出去 -
Webapis:我的理解是:浏览器进行处理的地方,比如网络请求,setTimeout等,等这个浏览器处理完了,那么就会进入队列Queue成为一个Message,等我们将stack里面处理完了,那么我们才会将这个Message放入Stack里
代码分析
首先拿MDN上的一个例子来说明:
function foo(b) {
let a = 10;
return a + b + 11;
}
function bar(x) {
let y = 3;
return foo(x * y);
}
console.log(bar(7));
先看运行状态:latentflip.com/loupe/?code…
理解:
- 我们可以很清晰的看到,首先
bar(7)这个函数被调用,那么想进入Stack - 在
bar()里面,因为调用了foo(),所以foo()函数进入 - 这时候
foo()函数执行,并且具有返回值,那么foo()出栈 - 这时候,我的
bar()也就有了返回值42,然后出栈 - 最后整个结束
那么我们现在来看一下setTimeout
栈溢出
所以这就可以解释,我们之前可能遇到的栈溢出问题:Maximum call stack size exceeded
function cb(){
return cb()
}
cb()
这个函数很明显,他就是会栈溢出
- 因为它一直在调用
cb这个函数,这个函数会一直进入到Stack里面 - 直到
栈满为止
阻塞
视频的解释很好理解:当我们在栈中执行一段很慢的东西的时候我们就会产生阻塞
为什么?
首先,我们必须知道js是一个单线程语言,我是这么理解的,也就是,你在这个函数还没有执行完成以后,其他的函数是无法影响这个函数的执行的,他只能等待,等我将这个函数执行完了,你才能执行
var foo = $.getSync("//foo.com")
var bar = $.getSync("//bar.com")
var qux = $.getSync("//qux.com")
console.log(foo)
console.log(bar)
console.log(qux)
这时候我们会看到,我们的栈中,首先进入foo,当foo执行完成,shift结束了,我们的bar才会进入,我们的foo执行了很长一段时间,这时候我们的bar和qux就只能等待,所以,产生了阻塞
setTimeout
setTimeout:其实是一个异步的函数他接受两个参数,一个是回调函数,一个是time
setTimeout(function(){
console.log("hello,summer")
},1000);
这段代码大家应该都很熟悉:就是一秒以后,输出hello,summer
当时这么说,其实并不准确,因为,这里的message,并不是指的是:等待的准确时间,其实它是指等待的最少事件
什么意思?
意思就是:当我们的stack里面是非空的时候,我们的setTimeOut里面的回调函数,即使到了那个time,也不会执行,他会变为一个message进入Queue进行等待,当我们Stack空了,那个回调函数才会执行
先看一段代码(来自MDN)
const s = new Date().getSeconds();
setTimeout(function cb() {
// 输出 "2",表示回调函数并没有在 500 毫秒之后立即执行
console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
}, 500);
while(true) {
if(new Date().getSeconds() - s >= 2) {
console.log("Good, looped for 2 seconds");
break;
}
}
先看是如何运行的:latentflip.com/loupe/?code…
我们可以看到
-
首先函数入栈
setTimeout这个函数入栈,当时他需要经过500ms以后才会执行,那么我们的webAPis先去进行计时,那么这是我们其实对setTimeout已经有一个交代了,那么setTimeout出栈, -
然后我们就会执行这个
while循环可能在这个时候,我的定时器已经完成了计时任务,需要执行回调函数了,当时他也是不可以随随便便就可以进入
stack里面的,他需要等stack全部都空了,才可以进行 -
所以,等2秒以后,
log结束了,那么才会执行回调cb,然后进行我们熟悉的入栈出栈,得到输出
其实这就是事件循环
事件循环
事件循环:就是查看stack和queue,当stack空的时候,将queue的队头元素放进去
那么我们现在来看点好玩的
setTimeout(callback,0)
我们定义:setTimeout的等待时间是
0ms
你可能会疑惑,都0ms了,不使用这个setTimeout不可以嘛?
那么想让我们看一段代码:
console.log("hello");
setTimeout(function cb(){
console.log("summer")
},0)
console.log("summer瓜瓜")
那么其实他的输出是
hello
summer瓜瓜
summer
我们来模拟一下是如何实现的:
- 首先,先输出
hello - 然后我们会遇到一个
setTimeout函数,然后,这个会webApis进行计时,那么他会立刻就被变为一个message,进入queue,因为我们栈里面是非空的,所以需要等待。 - 然后我们继续向下执行输出
summer瓜瓜 - 然后
queue将队头拿出来,也就是那个cb回调,然后就会进行入栈和出栈操作 - 然后输出
summer
总结一下:
setTimeout的时间设置为0,其实就是为了让代码到栈顶执行,或者等栈空了,再去执行
总结
事件循环:就是它关注stack和queue,然后将queue的队头放入stack里面
setTimeout:是异步的,他其实是webApis进行处理的,处理好了,将message放入queue里面,当stack空了以后,我们在将message所联系的函数,入栈和出栈
阻塞:其实就是栈里面的函数在进行很慢的操作,导致后面的函数执行不了