带你读懂event loop

什么是Event Loop

Event Loop涉及到代码的执行顺序问题,即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是我们经常使用 异步的原理。

简单来说,事件循环(event loop)就是 任务在主线程不断进栈出栈的一个循环过程。任务会在将要执行时进入主线程,在执行完毕后会退出主线程。

为什么会有event loop

因为js的时间循环,js是单线程。

js的事件循环

我们都知道 js是单线程的

为什么是单线程:作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程。

由于js是单线程的,只有当上一个任务完成之后才会继续完成下一个任务,如果前一个任务耗时很长,后一个任务就不得不一直等着,即阻塞

为了解决阻塞这一问题,将所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous),两种任务的差异就在于执行的优先级不同。event loop就是对任务的执行顺序做了详细的规范。

同步任务

在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;

异步任务

不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

如在某段程序中:主线程首先执行,遇到setTimeout时按照异步处理,1秒之后,setTimeout的回调函数会进入任务队列,主线程会继续运行,当主线程中的任务运行完成之后,会运行任务队列中的任务。

异步任务:异步任务分为宏任务和微任务。

常见的微任务有:Promise.then(),.then中的逻辑是微任务;process.nextTick(node环境)。

常见的宏任务有:setTimeout、setInterval、setImmediate(node环境)、xhr(发送网络请求),callback。

(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。

(2)主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。

(3)一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。

(4)主线程不断重复上面的第三步。

主线程从任务队列中读取事件,这个过程是不断循环的,所以整个的运行机制称为event loop

任务在哪里执行

无论是同步任务还是异步任务,都是在主线程执行。

任务执行原则

整体原则:先同步再异步,先执行宏任务再执行微任务

当同步任务执行完,先去事件队列查找执行一个宏任务,然后检查microtask队列是否为空,如果不为空则一次性执行所有微任务,微任务执行完继续去执行主线程任务,开启下一次事件循环。

宏任务:macrotask 可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行,每一个宏任务会从头到尾将这个任务执行完毕,不会执行其它)包括整体代码script,setTimeout,setInterval

微任务:可以理解是在当前 task 执行结束后立即执行的任务 包括Promise,process.nextTick

NodeJS的Event Loop

Node中的Event Loop是基于libuv实现的,而libuv是 Node 的新跨平台抽象层,libuv使用异步,事件驱动的编程方式,核心是提供i/o的事件循环和异步回调。libuv的API包含有时间,非阻塞的网络,异步文件操作,子进程等等。 Event Loop就是在libuv中实现的。

Node的Event loop一共分为6个阶段,每个细节具体如下:

  • timers: 执行setTimeout和setInterval中到期的callback。

  • pending callback: 上一轮循环中少数的callback会放在这一阶段执行。

  • idle, prepare: 仅在内部使用。

  • poll: 最重要的阶段,执行pending callback,在适当的情况下回阻塞在这个阶段。

  • check: 执行setImmediate(setImmediate()是将事件插入到事件队列尾部,主线程和事件队列的函数执行完成之后立即执行setImmediate指定的回调函数)的callback。

  • close callbacks: 执行close事件的callback,例如socket.on('close'[,fn])或者http.server.on('close, fn)。

Node 与浏览器的 Event Loop 差异

浏览器环境下:

microtask 的任务队列是每个 macrotask 执行完之后执行。

Node.js 环境下:

microtask 会在事件循环的各个阶段之间执行,也就是一个阶段执行完毕,就会去执行 microtask 队列的任务。

总结

浏览器和 Node 环境下,microtask 任务队列的执行时机不同

  • Node 端,microtask 在事件循环的各个阶段之间执行

  • 浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

参考链接:https://www.jianshu.com/p/9f1fbe935b06

https://blog.csdn.net/weixin_53504991/article/details/115455041

猜你喜欢

转载自blog.csdn.net/coinisi_li/article/details/128861694