学习记录之复盘——“消息队列和事件循环”

160 阅读4分钟

下述内容为个人学习的复习记录,主要是对一些网络资料的摘抄记录。以及一些自己的总结,可能会有一些错误,如有发现,欢迎指正!!!

学习内容主要来至:github.com/stephentian…

基本概念

一、JavaScript 的单线程

  • js 的主要用途是作为一个浏览器脚本语言,其主要功能与用户互动以及操作 DOM,单线程可以让这个流程变得更简单,多线程则会带来很多复杂的问题
  • HTML5 中提出了 Web Worker 标准,允许 js 创建多个线程,但是子线程受主线程控制,且不能操作 DOM,因此其本质还是单线程

二、JavaScript 的消息队列

1. 前置知识

js 运行时可视化表示

可视化表示

  • 通过上述图我们可以看出 js 在执行代码时,主要有三种内存空间:执行栈、消息队列、堆
  • 不同结构用途不同
    • 执行栈:执行同步任务
    • 消息队列:保存待执行的消息队列(异步任务)
    • 堆:存放对象

2. 为什么有消息队列

  • 单线程导致代码执行时,所有人都都需要排队执行,只有队列前面的任务执行结束之后,后面的任务才能执行。如果前一个任务需要很长的时间,那么后面的任务就要等待很长时间才能执行
  • 当遇到 IO 事件(用户输入、网络请求等事件)时,因为在等待 IO 时间时,CPU 是处于空闲的,如果没有消息队列机制,那么对 CPU 的运行效率将是一个很大的损失

3. 消息队列介绍

  • 为了解决上述问题,对不同任务进行划分为:同步任务、异步任务
  • 同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,后续任务才能执行
  • 异步任务:不进入主线程,而进入任务队列,只有任务队列通知主线程(将对应触发的事件消息入队到消息队列中),任务队列中的某个任务可以执行后,任务队列中的任务才会进入主线程中进行。
  • 当主线程中的任务空了之后,即执行栈要执行的任务都已经出栈后,就回去检查消息队列中是否有消息,如果存在则将对应的任务入栈到执行栈中执行。

三、事件循环(Event Loop)

  • 主线程从消息队列中读取事件这件事是一直在循环的,直到队列和栈都空了为止

四、定时器

  • 消息队列还可以放定时时间,定时器函数主要有setTimeout()setInterval(),其中setTimeout()设置规定时间后执行相应动作,setInterval()为每隔规定时间执行相应动作
  • H5 标准规定setTimeout第二个参数(延迟时间)不得低于 4ms,如果低于则会自动增加。如果是涉及到 DOM 的改动,通常不会立即执行,而是每 16ms 执行一次
  • 定时器设置的时间并不一定会被准时执行,因为事件循环机制,必须等待执行栈中的代码执行完毕后,主线程才会去执行消息队列中的任务。

五、宏任务与微任务

1. 宏任务(macrotask)

  • 可以称为 task,每次执行栈执行的代码就是一个宏任务(包括每一个从消息队列中取出的回调)
  • 宏任务的执行会从头到尾全部执行完毕,不会执行其他内容
  • 在一个 task 结束,下一个 task 开始之前,浏览器会对页面进行重新渲染
  • 主要有以下内容
    • 主代码块
    • setTimeout
    • setInterval
    • setImmediate
    • 一些事件的回调函数
    • ...

2. 微任务(microtaks)

  • 可以称为 job,是 task 执行结束后立即执行的任务
  • 因为是 task 结束之后立即执行的任务,所以会比 setTimeout 快,因为 setTimeout 是下一个被放到了消息队列中,属于下一个 task,而微任务在当前 task 结束后就会直接执行
  • 综上所述,在一个 task 结束之后,在渲染之前,上一个 task 产生的微任务,都会被执行完毕
  • 主要内容有
    • Promise
    • process.nextTick
  • 补充
    • process.nextTick 会在当前执行栈的尾部加入一个任务
    • setImmediate 会在当前消息队列的尾部添加一个事件
    • 在 node.js 中 process.nextTick 是先于 Promise 的