Node-JS专精12_01Eventloop

参考链接

Eventloop 之前的前置内容

操作系统相关

1
2
3
4
5
6
7
8
网页上有个输入框,当你按下键盘的 “J” 之后发生了什么

实际 “J” 下面有一个非常复杂的电路
当你按下 之后,会触发一个电流
这个电流会触发一个你按键的信息 如 101
然后把这个 信息 101 传递给 操作系统
操作系统知道这个 按键信息之后,它就会通知给你的浏览器
浏览器得到 “J” 按键信息之后,就会把这个 “J” 显示在你的输入框上

为什么讲这个:浏览器它会接受到系统给它的一个事件用户不停的触发事件,浏览器就不停的把用户输入渲染到输入框里

这个东西呢就叫做 “事件”

事件

接受各种外部设备发出的信号

  • 蓝牙事件
  • 鼠标事件
  • 键盘事件
  • USB事件

疑问?为什么你一插 USB操作系统马上就知道呢?有没有延迟呢?它是非常的快呢?还是一段时间一段时间的查呢?

  • 非常遗憾的是,操作系统并没有你想想的那么聪明
  • 它是如何知道 用户按下了 “J” 呢?

它实际上是 不停的等键盘,可能每隔5毫秒查看一下有没有按下这个键 这个过程 叫做 “轮询”

接下来我们看一下 浏览器的内容

  • 浏览器除了运行 JS 之外,还有些网络请求
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 假设你做了这样的事情
    console.log(1)
    console.log(2)
    console.log(3)
    ...
    console.log(10)
    ajax();
    console.log(11)
    console.log(12)
    ...
    console.log(20)


    当我们发送了请求之后,请求大概需要0.2秒得到结果,
    首先JS是单线程,那么在这请求 0.2 秒的时间内, ajax() 后面的代码有么有执行?

    有!!! 执行了

    那么此时 ajax 请求的 0.2秒 是谁在等?

或者说 等待请求结果是谁在处理 / 谁在轮询

  • 答案是:“C++”, 浏览器有一个轮询机制,每隔一段时间假设5毫秒就问 结果来了没?
  • 反正始终有一个东西在轮询 ,我们不管到底是 浏览器还是 操作系统这些细节。 反正不是JS在等就好了

所有的事件 应该如何通知给 JS

这个轮询的规则是什么?

搞清楚这些规则,你就明白什么是 Eventloop

  • 刚刚的内容讲的是浏览器,我们必须要回到 Node.js 和浏览器是差不多的东西
  • Node.js 可以执行js代码,浏览器也可以执行 js代码,Eventloop 是 Node.js的概念而不是 浏览器。 所以我们来看 Node.js

回到 Node.js

1
2
3
4
5
6
同样执行10行代码
然后发起一个异步任务 耗时2秒 setTimeout(fn,2000)
下面又执行20行代码


那么这个 setTimeout 到底是谁在做呢?

引入 Eventloop

  • 首先这个 Eventloop 会去监听这个 setTimeout,它会保证 一段时间后执行这个 fn

Eventloop 之前的铺垫

  • Eventloop 到底是真实存在的对象,还是一个虚拟的概念?

来个例子

1
2
3
4
5
6
7
8
9
假设人是有轮回的? 我们叫它 人生循环
那么人生循环有那些阶段呢?

生老病死 ==》 投胎 ==》 生老病死 ==》 投胎 。。。
什么时候是尽头,除非宇宙毁灭,什么时候开始,也不要太关心。

请问人生循环 是什么?

人在一生中处于不同阶段的过程

事件循环不是循环,它是状态变化的过程

Eventloop 事件循环的阶段

  • times (前端需要知道)
  • I/O callbacks
  • idle , prepare
  • poll (前端需要知道)
  • check (前端需要知道)
  • close callbacks

我们只需要清楚 times / poll / check 就够了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 times
2 poll
3 check


times => poll 会停留一段事件
poll => check
check => times


当我们执行 setTimeout(fn,2000) 时, 会把 fn 放入 times 的一个 数组里 (队列)同时记录 要在 2秒后执行 fn ,于是 js 就去做自己的事去了

setTimeout(fn,2000) 之后就会进入 poll 阶段 等待的过程中 他会去看时间,
刚才 js 说要在 2秒后执行 fn , 比如此时等了 500毫秒,发现不需要执行, 继续等 1000毫秒再去看, 继续等, 等到 2秒的时候 发现时间到了,
赶紧进入 times 阶段执行 fn, 虽然 poll之后要经过 check 阶段 ,但稍后再说
执行完 fn 之后 又回到 poll 阶段 继续等。 一般来说有最长等待时间 假设3秒 后就会进入 check 然后进入 times 然后继续进入 poll
所以 大部分时间都是在 poll 阶段 用来等

check 阶段是什么?

  • Node.js 除了 setTimeout 还有 setImmediate(fn2)
  • 当你调用 setImmediate(fn2) 的时候 你的 fn2 不会进入 times 阶段,会进入 check阶段
  • check 阶段也是有 一个队列的 主要是用来存 setImmediate 执行的函数的

来个考题, 先执行 fn 还是先执行 fn2

1
2
3
4
5
6
7
8
9
10
11
setTimeout(fn,0)
setImmediate(fn2)
// 答案是 fn2

/*
setTimeout(fn,0) 执行后 会进入 times 阶段 把 fn 放入 times 的队列里
然后看到下一句代码
setImmediate(fn2) 会把 fn2 放入 check 阶段的队列里 , 并立即执行队列里的fn2。
于是JS开始做事了, 它看到 check 队列里有个需要立即执行的 于是就不停了 ,直接 fn2执行掉
然后到 times 阶段 刚好发现 times 队列里 有个 0秒后执行,于是 执行 fn
*/

一个坑 开启 Eventloop() 这是需要消耗时间的

1
2
3
4
5
6
7
8
9
10
11
12
// 在 node环境下 运行这两句
setTimeout(()=>{console.log('fn')},0)
setImmediate(()=>{console.log('fn2')})

// 你会惊讶的发现 有的时候 fn fn2 有的时候 fn2 fn

// 为什么?
如果说 开启 Eventloop 很快 那么就是你分析的顺序 fn2 fn
如果 Eventloop 开启很慢 我们在执行js的时候 可能还没进入 times 实际上是先把 fn 加到 times 队列里 然后在开始第一阶段

这就刚好解释了 为什么顺序是不确定的?
就是看 进入 times 阶段的时候 fn 就存在 还是 进入 times 阶段的时候 fn 不存在

如何解决这种情况

1
2
3
4
5
6
setTimeout(()=>{
setTimeout(()=>{console.log('fn')},0)
setImmediate(()=>{console.log('fn2')})
},1000)

// 这样每次都是 fn2 fn

process.nextTick 是放到什么阶段

  • 首先它不属于 Eventloop 的任何一个阶段,官网说的,其他都是瞎扯的
  • process.nextTick 意思就是 当前阶段结束后马上执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setTimeout(()=>{
setTimeout(()=>{console.log('fn')},0)
setImmediate(()=>{console.log('fn2')})
process.nextTick(()=>{console.log('fn3')})
},1000)

// 注意!!! nextTick 不属于 Eventloop 任何阶段 , 它的意思是当前阶段马上执行
// setImmediate 会在 setTimeout之前
// setImmediate 是从 poll 阶段 进入 check 才执行
// nextTick 是当前阶段 poll 所以先 nextTick 在 check 阶段里的 然后 times 里的
/*
fn3
fn2
fn
*/

面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
setImmediate(()={
console.log("setImmediate1")
setTimeout(()=>{
console.log("setTimeout1")

},0)
})

setTimeout(()={
console.log("setTimeout2")
setImmediate(()=>{
console.log("setImmediate2")

},0)
})

答案是:
setImmediate1
setTimeout2
setTimeout1
setImmediate2

// 务必画图分析

总结

Eventloop 状态转移

  • Node.js 有六个阶段 简化为三个
    • times
    • poll
    • check
  • Chrome (一会,马上)(宏任务,微任务)

Node.js

  • setTimeout => times
  • setImmediate => check
  • nextTick => 当前阶段结束后执行

Chrome

  • setTimeout => 宏任务
  • promise.then => 微任务
    • 注意! 面试喜欢问 await
      1
      await 转换成 promise

面试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async function async1(){
console.log(1)
await async2()
console.log(2)
}

async function async2(){
console.log(3)
}

async1();

new Promise(function(resolve){
console.log(4)
resolve();
}).then(function(){
console.log(5)
})

/*
1
3
4
2
5
*/

首先改写所有的 await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
async function async1(){
console.log(1)
async2().then(f1)
function f1(){
console.log(2)
}
}

async function async2(){
console.log(3)
}

async1();

new Promise(function(resolve){
console.log(4)
resolve();
}).then(f2)

function f2(){
console.log(5)
}

/*
在看 首先 async1()执行 打印1
async2() 执行 打印3
async2().then() 把 f1 存入 微任务
new Promsie 开始 打印4
把 new Promise().then(f2) 的 f2 存入 微任务
开始执行微任务队列里的内容
f1 执行 打印 2
f2 执行 打印 5
*/

面试

  • 一定要画图
  • 一定要画图
  • 一定要画图

个性化题目的面试如何处理

只有某些公司会问,80%的公司不会问的题

  • PWA
  • echarts / d3.js
  • three.js
  • flutter
  • SSR

如何准备

平时用不到,公司太小众了, 但是面试问?

  • 先看公司招聘启事,如果明确说了,用到某某技术,你就去看文档,搞出一个 hello world
  • 因为大部分跟你一起面试的人根本就不会对这些东西做任何准备,你只要比他们多准备一点点,就可以了
  • 你不在乎你比别人多厉害,而在乎只要比别人厉害一点
  • 如果你没做,被问到,你连接话的地方都没有那岂不是很尴尬。所以你做个Hello World 都能改善局面