JavaScript中的事件循环与作业队列

时间:2021-1-8 作者:admin

  • 1 JavaScript简介
  • 2 初识Event Loop
    • 2.1 概览
    • 2.2 工作流程
  • 3 Job Queue
  • 4 总结
  • 资料

1 JavaScript简介

JavaScript是一门单线程(single-threaded)语言,也就是说,它在某一个时刻只能做一件事儿。

因此,我们需要一个合理的机制来调度任务,以避免阻塞。在这种背景下,Event Loop应运而生。

2 初识Event Loop

2.1 概览

Event Loop负责管控任务的顺畅执行,它会将一些费时较久或者需等待才启动的事件往后安排,从而提高用户的体验流畅度。

先看一张图,它代表着Event Loop机制。

  1. Web APIs:它包括DOM API, setTimeout, HTTP requests等等,能够帮助我们进行异步、非阻塞的操作。

  2. JavaScript的两大组件:

  • 堆内存(Memory Heap):此处用于内存分配。
  • 调用栈(Call Stack):它是一种后进先出(LIFO = Last In, First Out)的数据结构,记录着程序执行的位置。
  1. 回调队列(Callback Queue):又称消息队列(Messege Queue)、任务队列(Task Queue)、宏任务队列(Macro-task Queue)。它是一种先进先出(FIFO = First In, First Out)的数据结构,负责接收Web APIs传来的任务,当调用栈为空时,将回调队列中的任务推入调用栈。

其中的Web APIs、Event Loop以及Callback Queue都不属于js引擎,而是属于浏览器的JavaScript运行时环境或者Nodejs JavaScript运行时环境。

2.2 工作流程

以一个例子来简单地了解Event Loop。

console.log('Hi');setTimeout(function cb1() {     console.log('cb1');}, 5000);console.log('Bye');

在上面这个代码中,它的执行结果是:打印”Hi”,接着打印”Bye”,5s之后打印”cb1″。

之所以会产生这样的结果,是因为JavaScript中的Event Loop,它经历了以下几个过程:

  1. 初始态:Browser console和Call Stack(调用栈)均为空。
  2. console.log('Hi')被推进调用栈。
  3. console.log('Hi')被执行。
  4. console.log('Hi')被弹出调用栈。
  5. setTimeout(function cb1() { ... })被推进调用栈。
  6. setTimeout(function cb1() { ... })被执行。浏览器用Web APIs创建一个计时器,用来处理倒计时。
  7. setTimeout(function cb1() { ... })被弹出调用栈。
  8. console.log('Bye')被推进调用栈。
  9. console.log('Bye')被执行。
  10. console.log('Bye')被弹出调用栈。
  11. 5s之后,计时器完成计时,并将cb1这个回调函数推进Callback Queue(回调队列)。(这里需要特别注意:5s之后,是将cb1推入回调队列,而不是执行cb1,言下之意,cb1未必会在5s之后准时执行。)
  12. Event Loop把cb1从回调队列推进调用栈。
  13. cb1被执行,其中的console.log('cb1');被推入调用栈。
  14. console.log('cb1');被执行。
  15. console.log('cb1');被弹出调用栈。
  16. cb1被弹出调用栈。

3 Job Queue

除了宏任务队列,浏览器还引入了另一种队列,称为作业队列(Job Queue),又称微任务队列(Micro-task Queue)。

类别 具体
宏任务 setTimeout、setInterval、setImmediate
微任务 process.nextTick、Promise callback、queueMicrotask

微任务队列的执行优先级高于宏任务队列。以代码为例:

console.log('Start!');setTimeout(() => {  console.log('Timeout!');}, 0);Promise.resolve('Promise!') .then(res=>console.log(res));console.log('End!');

上述代码依次打印”Start!”、”End!”、”Promise!”、”Timeout!”。

第一步:执行同步任务console.log('Start!');

第二步:执行setTimeout,并使用Web APIs创建计时器

第三步:计时器完成计时,() => {console.log('Timeout!');}被推入宏任务队列。同时,执行Promise.resolve().then(...),并将res=>console.log(res));推入微任务队列。

第四步:执行console.log('End!')

第五步:执行微任务

第六步:执行宏任务

4 总结

  1. 事件循环机制用于调度任务,以保证流畅性。这其中需要多个组件协调配合,包括:Web APIs、调用栈、宏任务队列、事件循环。

  2. 除了宏任务队列之外,浏览器还引入了微任务队列,Promise回调属于微任务。微任务的优先级高于宏任务。

资料

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。