JS学习笔记 - 深入理解浏览器和Node.js中的宏任务、微任务、事件循环

JavaScript是单线程的

JavaScript是一个单线程的脚本语言,即JS的代码只能在一个进程中运行。也就是说,JS只能同时执行一个任务。但如果全部代码全是同步执行的,这会引发很严重的问题,比方说我们要从远端获取一些数据,难道要一直循环代码去判断是否拿到了返回结果么?

于是就有了异步事件的概念,注册一个回调函数,比如说发一个网络请求,我们告诉主程序等到接收到数据后通知我,然后我们就可以去做其他的事情了。

然后在异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,可以去执行。

浏览器线程简介

GUI渲染线程

负责渲染浏览器界面HTML元素

JS引擎线程

负责处理JavaScript脚本主程序

定时器触发线程

定时器setIntervalsetTimeout所在线程

浏览器事件线程

用来控制事件,如触发的onload、click事件等

http请求线程

XMLHttpRequest在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript引擎的任务队列中等待处理

宏任务和微任务

宏任务和微任务有哪些

宏任务

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • requestAnimationFrame(浏览器)
  • UI Rendering(浏览器)
  • I/O
  • script

微任务

  • Promise.then() Promise.catch() Promise.finally()
  • MutationObserver
  • Object.observe
  • async await
  • process.nextTick(Node)

浏览器的事件循环

Created with Raphaël 2.3.0 任务进入执行栈 是否为同步任务 主线程 任务全部执行完毕 读取任务队列中的结果,进入主线程执行 Event Table Event Queue yes no

同步和异步任务分别进入不同的执行“场所”,同步进入主线程,异步进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入任务队列(Event Queue)。主线程内的任务执行完毕为空,就去任务队列(Event Queue)读取对应的函数,进入主线程执行。

浏览器的宏任务、微任务机制

Created with Raphaël 2.3.0 将宏任务放入宏任务队列 是否存在微任务 将微任务放入微任务队列 宏任务执行结束 是否存在可执行的微任务 执行所有微任务并执行下一个宏任务 yes no yes

JS异步还有一个机制,就是遇到宏任务,先执行宏任务,将宏任务放入任务队列(event queue),然后再执行微任务,将微任务放入微任务队列(micro task queue),但是,这两个queue不是一个queue

执行顺序

主线程 >> 主线程上创建的微任务 >> 主线程上创建的宏任务 >> 主线程 >> 主线程上创建的微任务 >> 主线程上创建的宏任务 ……

示例

console.log(1)
setTimeout(()=>{
    
    
    console.log(2)
},0)
new Promise((resolve,reject)=>{
    
    
    console.log(3)
    resolve()
}).then(res=>{
    
    
    console.log(4)
})
console.log(5)
setTimeout(()=>{
    
    
    new Promise((resolve,reject)=>{
    
    
        console.log(6)
    }).then(res=>{
    
    
        console.log(7)
    })
},0)
console.log(8)

/*
控制台输出:
1
3
5
8
4
2
6

/// 7不输出因为该Promise状态仍为pending,且无法变为resolved
*/

Node.js的事件循环机制

Event Loop各阶段描述

阶段 描述
timers 用于处理setTimeoutsetInterval的回调函数
pending callbacks 执行某些系统操作的回调
idle,prepare Node内部使用,忽略
poll 等待新I/O事件触发,以及执行I/O相关回调
check 当poll阶段执行完成会进入到check阶段执行,该阶段的执行内容是所有setImmediate回调
close callbacks socket的异常关闭,close事件的回调会在该阶段执行

Node中的宏任务和微任务队列

宏任务队列

  • timers queue
  • pending callbacks queue
  • check queue
  • close queue

微任务队列

  • next Tick Queue
  • other Micro Queue

poll 轮询阶段

poll是一个核心阶段,等新I/O事件的触发,以及执行I/O相关回调

主要功能:

  • 执行到期的setTimeoutsetInterval的回调函数
  • 处理poll队列中的事件

工作机制:当没有timers被调度,分两种情况:

  1. 如果poll队列不为空,会挨个执行队列里的callback,直到队列为空,或达到系统限制
  2. 如果poll队列为空,分两种情况:
    • 如果执行了setImmediateeventLoop会结束poll阶段,进入到check阶段执行
    • 如果没有执行setImmediateeventLoop会等待callback进入队列

一旦poll队列为空,evetloop会检查timers,如果计时已到,event loop 会回到 timers 阶段,执行相应的回调函数.

执行顺序

  • timers
  • nextTickQueue
  • otherMicroQueue
  • pending callbacks
  • nextTickQueue
  • otherMicroQueue
  • idle,prepare
  • nextTickQueue
  • otherMicroQueue
  • poll(可能返回到timers阶段)
  • nextTickQueue
  • otherMicroQueue
  • check
  • nextTickQueue
  • otherMicroQueue
  • close callbacks

示例

console.log(0);

setTimeout(() => {
    
    
  console.log(1);
  setTimeout(() => {
    
    
    console.log(2);
  }, 0);
  setImmediate(() => {
    
    
    console.log(3);
  })
  process.nextTick(() => {
    
    
    console.log(4);  
  })
}, 0);

setImmediate(() => {
    
    
  console.log(5);
  process.nextTick(() => {
    
    
    console.log(6);  
  })
})

setTimeout(() => {
    
              
  console.log(7);
  process.nextTick(() => {
    
    
    console.log(8);   
  })
}, 0);

process.nextTick(() => {
    
    
  console.log(9);  
})

console.log(10);
/* Node.js 控制台输出:
0
10
9
1
4
7
8
5
6
3
2
*/

猜你喜欢

转载自blog.csdn.net/m0_52761633/article/details/120693993