event loop

2020-05-28  本文已影响0人  littleyu

参考

有空需要自己把英文文档翻译一遍,锻炼英语能力。

首先讲讲操作系统有关的知识

当我们按下键盘的时候,发生了什么, 操作系统是怎么知道的?

到现在我也不知道他是怎么知道的,通过搜索发现是这样的:键盘下面有一个电路,当我们按下某个键的时候,就有触发一个信息,例如: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 启动时,会做这几件事

  1. 初始化 event loop
  2. 开始执行脚本(或者进入 REPL,本文不涉及 REPL)。这些脚本有可能会调用一些异步 API、设定计时器或者调用 process.nextTick()
  3. 开始处理 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队列

  1. timer [fn2]
  2. poll
  3. check [fn1]

执行 fn1 的时候先打印出了 setImmediate1 ,然后遇到setTimeout 函数即 fn3,所以把 fn3 放进 timer 队列,event loop 执行完了check 里的任务,进入下一个 timer 阶段,

  1. timer [fn2, fn3]
  2. poll
  3. check [fn1]

所以,在 timer 阶段 fn2 先执行,打印出了 setTimeout2,然后把 fn4 放入 check 队列,注意:此时 timer 阶段尚未结束,必须先执行完 timer 阶段所有函数才能才能进入下一个阶段,所以紧接着打印出 setTimeout1,最后打印出 setImmediate2

  1. timer [fn2, fn3]
  2. poll
  3. check [fn1, fn4]

照着这张图,无脑解决所有 event loop 的题目。

浏览器,就相对来说比较简单了,出了同步代码,就是异步代码(宏任务--(马上)、微任务--(一会))

macrotasks:setTimeout,setInterval, setImmediate, I/O, UI渲染
microtasks:Promise(大部分都是用process.nextTick实现的), process.nextTick Object.observe(已经没人用了), MutationObserver

看题:


解析:

上一篇下一篇

猜你喜欢

热点阅读