事件循环详细版

事件循环:

在这里插入图片描述

javascript是一门单线程的非阻塞的脚本语言。js为什么是单线程的语言?是因为js的执行引擎只有一个线程,不会在执行期间开启新的线程,而非浏览器是单线程的。浏览器是多线程的。

Event Loop是javascript的执行机制,也是js实现异步的一种方法。

单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务,如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。保证了程序执行的一致性。不管是什么新框架新语法糖实现的所谓异步,其实都是用同步的方法去模拟的

而非阻塞则是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。

****事件循环**:js主线程有一个执行栈,所有js代码都会在执行栈里运行,当一个脚本第一次执行的时候,js引擎会解析这段代码,并将其中的同步代码按照执行顺序加入执行栈中,如果碰到一些异步代码(比如setTimeout,ajax,promise.then以及用户点击等操作),浏览器就会把这些代码交给异步线程去执行,主线程继续执行栈中的代码,当异步线程里的代码执行完成后该线程就会将它的异步回调函数放到任务队列(又称事件队列,消息队列)中等待执行。当主线程执行完栈中的所有代码后,它就会检查任务队列是否有任务要执行,如果有任务要执行就将该任务放到执行栈中去执行。如果当前任务队列为空就会一直等待任务到来。

**执行宏任务执行过程中出现了微任务,会去执行微任务。**执行每个宏任务之前都要检查下微任务队列是否有任务,如果有,优先执行微任务队列。

常见宏任务有:

  1. script (可以理解为外层同步代码)、ajax请求回调
  2. setTimeout/setInterval
  3. setImmediate(Node.js)

常见微任务有:

  1. Promise、await(promise的语法糖,返回一个promise对象)
  2. process.nextTick(Node.js)
  3. MutaionObserver

Node.js也是单线程的Event Loop,但是它的运行机制不同于浏览器环境。Node的Event Loop是阶段的转移过程。

在这里插入图片描述

六个阶段:

timers: 执行setTimeoutsetInterval的回调

pending callbacks: 执行延迟到下一个循环迭代的 I/O 回调

idle, prepare: 仅系统内部使用

poll: 检索新的 I/O 事件;执行与 I/O 相关的回调。事实上除了其他几个阶段处理的事情,其他几乎所有的异步都在这个阶段处理。(等待)

check: setImmediate在这里执行

close callbacks: 一些关闭的回调函数,如:socket.on('close', ...)

每个阶段都有一个自己的先进先出的队列,只有当这个队列的事件执行完或者达到该阶段的上限时,才会进入下一个阶段。在每次事件循环之间,Node.js都会检查它是否在等待任何一个I/O或者定时器,如果没有的话,程序就关闭退出了。

setImmediatesetTimeout

除了第一次的启动不确定之外,正常情况下处于poll阶段时,setImmediate会比定时器先执行:

console.log('outer');

setTimeout(() => {
  setTimeout(() => {
    console.log('setTimeout');
  }, 0);
  setImmediate(() => {
    console.log('setImmediate');
  });
}, 0);

image-20200322210304757

  1. 外层是一个setTimeout,所以执行他的回调的时候已经在timers阶段了

  2. 处理里面的setTimeout,因为本次循环的timers正在执行,所以他的回调其实加到了下个timers阶段

  3. 处理里面的setImmediate,将它的回调加入check阶段的队列

  4. 外层timers阶段执行完,进入pending callbacksidle, preparepoll,这几个队列都是空的,所以继续往下

  5. 到了check阶段,发现了setImmediate的回调,拿出来执行

  6. 然后是close callbacks,队列是空的,跳过

  7. 又是timers阶段,执行我们的console

理解成正常情况下,先执行check,再执行timer。

console.log('outer');

setTimeout(() => {
console.log('setTimeout');
}, 0);

setImmediate(() => {
console.log('setImmediate');
});

我们来运行下看看效果:

image-20200322214105295

好像是setTimeout先输出来,我们多运行几次看看:

image-20200322214148090

两者之间的出现的顺序不一定。node.js里面setTimeout(fn, 0)会被强制改为setTimeout(fn, 1),这在官方文档中有说明。(说到这里顺便提下,HTML 5里面setTimeout最小的时间限制是4ms)。

  1. 外层同步代码一次性全部执行完,遇到异步API就塞到对应的阶段
  2. 遇到setTimeout,虽然设置的是0毫秒触发,但是被node.js强制改为1毫秒,塞入times阶段
  3. 遇到setImmediate塞入check阶段
  4. 同步代码执行完毕,进入Event Loop
  5. 先进入times阶段,检查当前时间过去了1毫秒没有,如果过了1毫秒,满足setTimeout条件,执行回调,如果没过1毫秒,跳过
  6. 跳过空的阶段,进入check阶段,执行setImmediate回调

通过上述流程的梳理,我们发现关键就在这个1毫秒,如果同步代码执行时间较长,进入Event Loop的时候1毫秒已经过了,setTimeout执行,如果1毫秒还没到,就先执行了setImmediate。每次我们运行脚本时,机器状态可能不一样,导致运行时有1毫秒的差距,一会儿setTimeout先执行,一会儿setImmediate先执行。**但是这种情况只会发生在还没进入timers阶段的时候。**像我们第一个例子那样,因为已经在timers阶段,所以里面的setTimeout只能等下个循环了,所以setImmediate肯定先执行。

同理的还有其他poll阶段的API也是这样的,

var fs = require(‘fs’)

fs.readFile(__filename, () => {
setTimeout(() => {
console.log(‘setTimeout’);
}, 0);
setImmediate(() => {
console.log(‘setImmediate’);
});
});

这里setTimeoutsetImmediatereadFile的回调里面,由于readFile回调是I/O操作,他本身就在poll阶段,所以他里面的定时器只能进入下个timers阶段,但是setImmediate却可以在接下来的check阶段运行,所以setImmediate肯定先运行,他运行完后,去检查timers,才会运行setTimeout

process.nextTick()

process.nextTick()是一个特殊的异步API,他不属于任何的Event Loop阶段。事实上Node在遇到这个API时,Event Loop根本就不会继续进行,会马上停下来执行process.nextTick(),这个执行完后才会继续Event Loop。(在当前阶段结束,准备跳到下一个阶段之前去执行process.nexttick()函数)

var fs = require('fs')

fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);

setImmediate(() => {
    console.log('setImmediate');
    
    process.nextTick(() => {
      console.log('nextTick 2');
    });
});

process.nextTick(() => {
  console.log('nextTick 1');
});

});

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hKqJ2bko-1602035479932)(C:\Users\shao\AppData\Roaming\Typora\typora-user-images\image-20201006204427255.png)]

我们代码基本都在readFile回调里面,他自己执行时,已经在poll阶段

遇到setTimeout(fn, 0),其实是setTimeout(fn, 1),塞入后面的timers阶段

遇到setImmediate,塞入后面的check阶段

遇到nextTick,立马执行,输出’nextTick 1’

到了check阶段,输出’setImmediate’,又遇到个nextTick,立马输出’nextTick 2’

到了下个timers阶段,输出’setTimeout’

https://juejin.im/post/6844904100195205133#heading-11

settimeout的理解

setTimeout这个函数,是经过指定时间后,把要执行的任务加入到Event Queue中,又因为是单线程任务要一个一个执行,如果事件队列中前面的任务需要的时间太久,那么只能等着,导致真正的延迟时间远远大于设定的时间。

setTimeout(fn,0)的含义是,不用等待进入时间宏队列中,栈为空就马上执行。

setInterval是循环的执行。对于执行顺序来说,setInterval会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。

唯一需要注意的一点是,对于setInterval(fn,ms)来说,我们已经知道不是每过ms秒会执行一次fn,而是每过ms秒,会有fn进入Event Queue。一旦**setInterval的回调函数fn执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了**。
node.js中的事件循环

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值