likes
comments
collection
share

究竟何为event loop?!为什么总是把它和异步任务一起谈

作者站长头像
站长
· 阅读数 69

众所周知,JavaScript是单线程的,但是现在也经常需要JavaScript进行一些异步的任务,这时候就会用到setTimeoutasync awaitpromise等api。可能你就要问了,那么JavaScript到底是怎么实现异步任务的呢?JavaScript不是单线程嘛?而且这篇文章不是在说事件循环嘛,这和JS实现异步任务又有什么关系?

先别急,我们先深究一下,到底什么是”单线程“?经常和”线程“相提并论的”进程“又是什么?

进程和线程

官方概念:

  • 进程(process):计算机运行的程序
  • 线程(thread):操作系统能运行运算调度的最小单位,是进程中的实际运作单位

可以简单理解为每当我们在计算机里面运行的一个软件程序,就会开启一个新进程来运行该程序(有时候也会是多个进程,浏览器就是多进程)。其中一个进程里面可以拥有多个线程,并且进程中可以并发多个线程,让每条线程分别执行不同的任务。而每个线程内是按照单一顺序来执行代码的。究竟何为event loop?!为什么总是把它和异步任务一起谈

单线程的JavaScript

所以前面说的:JavaScript是单线程,就意味着JavaScript只能按照顺序执行代码,如果前面的代码执行耗时长,后面的代码只能等待,导致堵塞现象。想象一下前面的代码发了个请求要等到几十秒........后面的全部代码都要等着.....

这时候你可能又想问了:为什么JavaScript要设置为单线程啊?一开始就设置为多线程不香吗?

这和JavaScript的用途有关,它作为浏览器脚本语言,有一个核心的用途是:与用户互动以及操作DOM。想象一下,如果JavaScript是多线程,我在一个线程上对某个DOM节点添加东西,又在另一个线程把这个DOM删除了,那究竟是先删除还是先添加呢?为了避免这样的复杂情况,JavaScript只能设置为单线程。

但是为了避免前面提及的堵塞现象,又需要异步任务。

那么又回到最开头的问题:那么JavaScript到底是怎么实现异步任务的呢?

提供异步能力的runtime

JavaScript的异步能力其实是由运行环境(JavaScript runtime)所提供的。举个例子,JavaScript可以在 chrome 中执行,也可以在node中执行,那么chromenode都是JavaScript Runtime

如图是一个浏览器JavaScript runtimenodejs有所不同)的简易图,让我们简单看看这个结构:究竟何为event loop?!为什么总是把它和异步任务一起谈

可以把JavaScript runtime理解成一个进程区域(线程池),主要包括JS引擎(JavaScript Engine)和Web API、回调队列(图中的task queuemicrotask queue),它们三者的主要作用如下(详细可以参考:https://juejin.cn/post/6844903908452597768):

  • JS引擎:

    • 内存堆(memory heap)分配内容来存储引用类型的实际值等
    • 调用栈(call stack)将JS源码解析、转行成一个个可执行单元(前面提到的JS代码是单线程就是体现在该调用栈中:所有JS代码都在该唯一的调用栈按照顺序执行)
  • Web API:前面提到的为JS本身提供异步能力的特性集合就是浏览器的Web API,其实setTimeoutsetInternal等异步api并不是js自身带有的,是浏览器提供的
  • 回调队列:该模块协助Web API处理异步操作,用于保存已经叫用的callback

除了在这三个模块外,还有一个事件循环(Event loop),它就是这次的主角,它负责JS实现异步的机制和“检察官”

说到这里,我们其实还是不知道javascript runtime中的event loop是怎么为我们提供异步能力的,这下我们先对浏览器event loop进行深挖了~(node中环境有所不同)

浏览器的事件循环

先来看看event loop,它是javascript runtime的调节机制,它的主要作用是:

The job of the Event loop is to look into the call stack and determine if the call stack is empty or not(查看调用栈并且判断它目前状态是否为空). If the call stack is empty, it looks into the message queue to see if there’s any pending callback waiting to be executed(如果调用栈是空的,它又会去看消息队列/回调队列是否有回调在等待执行)

究竟何为event loop?!为什么总是把它和异步任务一起谈

再了解一下JavaScript中同步任务和异步任务的执行规则,同步和异步任务分别进入不同的执行"场所":

  1. 要执行的同步任务会被放入调用栈(call stack)里排队执行:只有前一个任务执行完毕,才能接着执行下一个任务
  2. 异步任务不会进入主线程,而是再有了运行结果后在任务队列(task queue)放置一个事件
  3. 如果调用栈里面所有的同步任务都执行完毕,系统就去任务队列中看看还有哪些异步任务需要执行,将已经结束的异步任务放置在任务队列中的事件放进调用栈,开始执行
  4. 上述三步不断地循环,因此变成了“事件循环”

event loop以及异步任务执行规则结合来看,是不是事情看上去就明了了不少了?但还没完,让我们再细看一下我们的任务队列

浏览器的宏任务和微任务

其实任务队列细分为微任务队列(microtask queue)和宏任务队列(macrotask queue),不同类型的异步任务事件会分别进入不同的任务队列,而不同的任务队列执行规则也不一样。

宏任务一般包括:ajaxsetTimeoutsetIntervalDOM监听UI Rendering

微任务一般包括:Promise的then回调queueMicrotask()究竟何为event loop?!为什么总是把它和异步任务一起谈

而两个队列的执行规则如下:

  1. 当调用栈为空,系统来检查任务队列时,会先看当前的微任务队列情况
  2. 若当前微任务队列里有事件,会将队列里所有的事件都执行完
  3. 再去执行当前宏任务队列里面的一个事件
  4. 执行了一个事件后再检查此时有没有新的微任务事件,有的话要将微任务事件执行完才能执行下一个宏任务

.....如此循环

说到这儿,你应该也清楚JS实现异步和事件循环的关系,以及事件循环具体是怎么执行了的吧~如有写的不准确的地方欢迎提出来

(ps:其实nodejs里的事件循环和浏览器有些不一样,以后有空再写一篇捋一下~)

文章参考:

https://www.youtube.com/watch?v=8aGhZQkoFbQ

https://juejin.cn/post/7083286147920560158

https://www.ruanyifeng.com/blog/2014/10/event-loop.html

https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif

转载自:https://segmentfault.com/a/1190000043490691
评论
请登录