什么是事件循环?
事实上我把事件循环理解成我们编写的JavaScript和浏览器或者Node之间的一个桥梁。
进程和线程
线程和进程是操作系统中的两个概念:
进程(process):计算机已经运行的程序;
线程(thread):操作系统能够运行运算调度的最小单位;
操作系统类似于一个工厂;
工厂中里有很多车间,这个车间就是进程;
每个车间可能有一个以上的工人在工厂,这个工人就是线程
浏览器和JavaScript
我们经常会说JavaScript是单线程的,但是JavaScript的线程应该有自己的容器进程:浏览器或者Node。
目前多数的浏览器其实都是多进程的,当我们打开一个tab页面时就会开启一个新的进程,这是为了防止一个页面卡死而造成所有页面无法响应,整个浏览器需要强制退出;
每个进程中又有很多的线程,其中包括执行JavaScript代码的线程;
但是JavaScript的代码执行是在一个单独的线程中执行的:
这就意味着JavaScript的代码,在同一个时刻只能做一件事;如果这件事是非常耗时的,就意味着当前的线程就会被阻塞
浏览器的事件循环
1,先执行初始化代码,将回调函数给对应模块
2,当事件发生后,管理模块将回调函数放入对应队列(宏队列,微队列)
3,初始化代码执行完后,依次读取队列里回调函数(队列读取顺序如下)
async,await
也就是说await后的代码,看作初始化代码,立即执行
await下一句,加入微任务队列
node事件循环
和浏览器差不多,分的更详细罢了,也分微宏任务队列,
顺序:初始化代码(main script) -> nextTicks() -> 其他微任务(promise.then) -> times(定时器等,不包括立即执行函数) -> IO -> 立即执行函数 ->close等
浏览器中的EventLoop是根据HTML5定义的规范来实现的,不同的浏览器可能会有不同的实现,而Node中是由libuv实现的
事件循环像是一个桥梁,是连接着应用程序的JavaScript和系统调用之间的通道:
无论是我们的文件IO、数据库、网络IO、定时器、子进程,在完成对应的操作后,都会将对应的结果和回调函数放到事件循环(任务队列)中;事件循环会不断的从任务队列中取出对应的事件(回调函数)来执行
一次完整的事件循环Tick分成很多个阶段:
定时器(Timers):本阶段执行已经被 setTimeout() 和 setInterval() 的调度回调函数。
待定回调(Pending Callback):对某些系统操作(如TCP错误类型)执行回调,比如TCP连接时接收ECONNREFUSED。
idle, prepare:仅系统内部使用。
轮询(Poll):检索新的 I/O 事件;执行与 I/O 相关的回调;
检测:setImmediate() 回调函数在这里执行。
关闭的回调函数:一些关闭的回调函数,如:socket.on('close', ...)
面试题解析
大概率是settimeout setimmediate
因为虽然settimeout是延时0秒,但它实际上是主线程执行完后立即执行它,会有延时
当初始化事件循环时间较短,而延时较高时,settimeout还没加入队列,就先执行立即执行函数了