有空需要自己把英文文档翻译一遍,锻炼英语能力。
首先讲讲操作系统有关的知识
当我们按下键盘的时候,发生了什么, 操作系统是怎么知道的?
到现在我也不知道他是怎么知道的,通过搜索发现是这样的:键盘下面有一个电路,当我们按下某个键的时候,就有触发一个信息,例如:0101,这个数字就会被传给操作系统,操作系统知道了以后,就会传给浏览器,浏览器知道了以后,把内容显示在 input
框里。
为什么讲这个呢,浏览器会接受到系统给他的 事件,这个是第一个概念,操作系统会接受到各种信号,在分配给其他软件。
这其中又有一个疑问,操作系统是在接受这个信号的时候,是立马就知道的呢?, 还是每隔一段时间问一次呢?
非常遗憾,操作系统并没有那么智能,他只能不停的等键盘触发,比如每隔5mm,看看键盘有没有触发,不停地循环,当用户按了以后就放进一个队列,操作系统每隔5mm就会发现并执行,这个行为就叫 轮询。
接下来看看JS
浏览器不止运行JS,还要发起一些网络请求,比如:当浏览器执行JS代码的时候,遇到中间有一个AJAX请求,需要耗时0.2s, JS是一个单线程的,单线程就是不可能同时执行两个任务的,所以应该要等ajax发送接受完在继续执行接下来的代码呢?还是继续执行以后的代码,在回头接受AJAX呢?**, 两个方向只能二选一。
通过认知我们知道,JS选得是第二条路,先执行接下来的代码,但是疑问又来了,请问这0.2s是谁在等呢,谁知道(轮询)这个请求成功了呢?总得有人在轮询这个请求成功了没把(开头说过没那么智能),这段时间JS在做其他事情,首先排除JS。
所以是 C++,写浏览器的核心机制(这对前端来说已经超纲了),不停地去看网络到了没,或者是操作系统,不管是谁在做这个事情,反正不是JS,暂时不管这些细节。
所以这个轮询是不是该遵循一种机制呢?(得有规律的告诉JS网络请求到了把)。
接下来我们就讲讲这个规则
当JS遇到一个异步任务的时候,其实JS什么都没做,他只是给C++发了一个消息,然后继续做自己的事情,C++在忙的时候,有一定的规则、顺序。然后把AXAJ返回的时间告诉JS,JS再继续执行。
现在回到node.js,nodeJs可以执行JS代码,浏览器也能执行JS代码,是差不多的,但是event loop是nodeJS的概念,而不是浏览器的, 所以我们这里讲讲nodeJS的event loop。
当 Node.js 启动时,会做这几件事
- 初始化 event loop
- 开始执行脚本(或者进入 REPL,本文不涉及 REPL)。这些脚本有可能会调用一些异步 API、设定计时器或者调用 process.nextTick()
- 开始处理 event loop
如何处理 event loop 呢?下图给出了一个简单的概览:
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
其中每个方框都是 event loop 中的一个阶段。
为了前端理解、简化为三个阶段
其中 poll 阶段会停留一段时间(如网络请求、文件回调)
分析:当我们执行 setTimeout 的时候,event loop 有没有开启?也就是先开启 event loop 还是,先执行代码呢?
从原文中看到,这并不确定(开启event loop,执行JS都需要时间,如:先开启了 event loop,但JS还没开始执行)。
所以。这才有了一道著名的考题:setTimeout(fn, 0) 和 setImmediate(fn2)【setImmediate属于check阶段】,fn先执行还是fn2先执行?
以前我们都是靠背,setImmediate 优先级高,先执行。但是从上图分析,看event loop的开启时间,如果event loop 在 timer 阶段,那就会立马执行setTimeout 函数,再执行 setImmediate,如果在poll阶段,则会先执行setImmediate 再执行 setTimeout,所以答案是不确定哪个先执行,当然也就只有刚开始的时候才会出现这种情况(因为不确定event loop 什么时候会开启),但是当一切都准备就绪的时候,即:
setTimeout(() => {
setTimeout(fn, 0)
setImmediate(fn2)
}, 1000)
肯定就是setImmediate会先执行,因为大部分时间都会停留在poll阶段,所以也就有了我们平时记的 setImmediate 优先级高。
这里再扯一个process.nextTick,他不属于任何一个阶段,代表在某个阶段立马执行,如:
setTimeout(() => {
process.nextTick(fn3)
setTimeout(fn, 0)
setImmediate(fn2)
}, 1000)
// fn3 => fn2 => fn
setTimeout(() => {
process.nextTick(fn4)
setTimeout(() => {
fn
process.nextTick(fn3)
}, 0)
setImmediate(fn2)
}, 1000)
// fn4 => fn2 => fn => fn3
我买再来看一题
共四个函数:
第一轮执行,fn1放进check队列,fn2放进timer队列
- timer [fn2]
- poll
- check [fn1]
执行 fn1 的时候先打印出了 setImmediate1 ,然后遇到setTimeout 函数即 fn3,所以把 fn3 放进 timer 队列,event loop 执行完了check 里的任务,进入下一个 timer 阶段,
- timer [fn2, fn3]
- poll
- check [
fn1]
所以,在 timer 阶段 fn2 先执行,打印出了 setTimeout2,然后把 fn4 放入 check 队列,注意:此时 timer 阶段尚未结束,必须先执行完 timer 阶段所有函数才能才能进入下一个阶段,所以紧接着打印出 setTimeout1,最后打印出 setImmediate2
- timer [
fn2,fn3]- poll
- check [
fn1, fn4]
照着这张图,无脑解决所有 event loop 的题目。
浏览器,就相对来说比较简单了,出了同步代码,就是异步代码(宏任务--(马上)、微任务--(一会))
macrotasks:setTimeout,setInterval, setImmediate, I/O, UI渲染
microtasks:Promise(大部分都是用process.nextTick实现的), process.nextTick Object.observe(已经没人用了), MutationObserver
看题:
解析:
网友评论