Node事件循环详解

Node 事件循环

经典真题

  • 请简述一下 Node.js 中的事件循环,和浏览器环境的事件循环有何不同?

Node.js 与浏览器的事件循环有何区别?

进程与线程

我们经常说 JavaScript 是一门单线程语言,指的是一个进程里只有一个主线程,那到底什么是线程?什么是进程?

首先需要把这个问题搞明白。

进程是 CPU 资源分配的最小单位,而线程是 CPU 调度的最小单位。举个例子,看下面的图:

image-20211015112136231
  • 进程好比图中的工厂,有单独的专属自己的工厂资源。

  • 线程好比图中的工人,多个工人在一个工厂中协作工作,工厂与工人是 1:n 的关系。也就是说一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

  • 工厂的空间是工人们共享的,这象征一个进程的内存空间是共享的,每个线程都可用这些共享内存

  • 多个工厂之间独立存在。

接下来我们回过头来看多进程和多线程的概念:

  • 多进程:在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态。多进程带来的好处是明显的,比如你可以听歌的同时,打开编辑器敲代码,编辑器和听歌软件的进程之间丝毫不会相互干扰。

  • 多线程:程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

Chrome 浏览器中为例,当你打开一个 Tab 页时,其实就是创建了一个进程。

image-20211015112546949

一个进程中可以有多个线程,比如渲染线程、JS 引擎线程、HTTP 请求线程等等。当你发起一个请求时,其实就是创建了一个线程,当请求结束后,该线程可能就会被销毁。

浏览器内核

简单来说浏览器内核是通过取得页面内容、整理信息(应用 CSS )、计算和组合最终输出可视化的图像结果,通常也被称为渲染引擎。

浏览器内核是多线程,在内核控制下各线程相互配合以保持同步,一个浏览器通常由以下常驻线程组成:

  • GUI 渲染线程
  • JavaScript 引擎线程
  • 定时触发器线程
  • 事件触发线程
  • 异步 http 请求线程
GUI 渲染线程
  • 主要负责页面的渲染,解析 HTMLCSS,构建 DOM 树,布局和绘制等。
  • 当界面需要重绘或者由于某种操作引发回流时,将执行该线程。
  • 该线程与 JS 引擎线程互斥,当执行 JS 引擎线程时,GUI 渲染会被挂起,当任务队列空闲时,主线程才会去执行 GUI 渲染。
JavaScript 引擎线程
  • 该线程当然是主要负责处理 JavaScript 脚本,执行代码。
  • 也是主要负责执行准备好待执行的事件,即定时器计数结束,或者异步请求成功并正确返回时,将依次进入任务队列,等待 JS 引擎线程的执行。
  • 当然,该线程与 GUI 渲染线程互斥,当 JS 引擎线程执行 JavaScript 脚本时间过长,将导致页面渲染的阻塞。
定时触发器线程
  • 负责执行异步定时器一类的函数的线程,如:setTimeout、setInterval
  • 主线程依次执行代码时,遇到定时器,会将定时器交给该线程处理,当计数完毕后,事件触发线程会将计数完毕后的事件加入到任务队列的尾部,等待 JS 引擎线程执行。
事件触发线程
  • 主要负责将准备好的事件交给 JS 引擎线程执行。

比如 setTimeout 定时器计数结束, ajax 等异步请求成功并触发回调函数,或者用户触发点击事件时,该线程会将整装待发的事件依次加入到任务队列的队尾,等待 JS 引擎线程的执行。

异步 http 请求线程
  • 负责执行异步请求一类的函数的线程,如:Promise、fetch、ajax 等。
  • 主线程依次执行代码时,遇到异步请求,会将函数交给该线程处理,当监听到状态码变更,如果有回调函数,事件触发线程会将回调函数加入到任务队列的尾部,等待 JS 引擎线程执行。

浏览器中的事件循环

宏任务和微任务

事件循环中的异步队列有两种:宏任务( macro )队列和微任务( micro )队列。

宏任务队列有一个,微任务队列只有一个

  • 常见的宏任务有:setTimeout、setInterval、requestAnimationFrame、script等。
  • 常见的微任务有:new Promise( ).then(回调)、MutationObserver 等。
事件循环流程

一个完整的事件循环过程,可以概括为以下阶段:

image-20211015121213384
  • 一开始执行栈空,我们可以把执行栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。微任务队列空,宏任务队列里有且只有一个 script 脚本(整体代码)。

  • 全局上下文( script 标签)被推入执行栈,同步代码执行。在执行的过程中,会判断是同步任务还是异步任务,通过对一些接口的调用,可以产生新的宏任务与微任务,它们会分别被推入各自的任务队列里。同步代码执行完了,script 脚本会被移出宏任务队列,这个过程本质上是队列的宏任务的执行和出队的过程。

  • 上一步我们出队的是一个宏任务,这一步我们处理的是微任务。但需要注意的是:当一个宏任务执行完毕后,会执行所有的微任务,也就是将整个微任务队列清空。

  • 执行渲染操作,更新界面

  • 检查是否存在 Web worker 任务,如果有,则对其进行处理

  • 上述过程循环往复,直到两个队列都清空

宏任务和微任务的执行流程,总结起来就是:

当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。

执行流程如下图所示:

image-20211015114206131

这里我们可以来看两道具体的代码题目加深理解:

console.log('script start');
setTimeout(function() {
   
    console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
   
    console.log('promise1');
}).then(function() {
   
    console.log('promise2');
})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值