深入理解JS单线程

解决一个问题:JS运行时异步任务的处理在浏览器和Node环境有没有差别?

前言

Event Loop 即事件循环,是指浏览器的一种解决 JavaScript 单线程运行时不会阻塞的一种机制,也就是我们经常使用异步的原理。

为什么要弄懂 Event Loop

  • 弄懂 JavaScript 的运行机制。
  • 现在在前端领域各种技术层出不穷,掌握底层原理,以不变应万变。

Event Loop 是什么

event loop 是一个执行模型,在不同的地方有不同的实现。浏览器和 Node.js 基于不同的技术实现了各自的Event Loop。

  • 浏览器的Event Loop是在 html5的规范 中明确定义。
  • NodeJS的Event Loop是基于libuv实现的。可以参考Node的官方文档以及libuv的官方文档
  • libuv已经对Event Loop做出了实现,而HTML5规范中只是定义了浏览器中Event Loop的模型,具体的实现留给了浏览器厂商。
浏览器架构图

浏览器架构

Node.js 架构图

Node.js 架构

宏任务和微任务

宏任务macrotask,也叫 tasks。 一些异步任务的回调会依次进入macro task queue,等待后续被调用,这些异步任务包括:

  • setTimeout
  • setInterval
  • setImmediate (Node独有)
  • requestAnimationFrame (浏览器独有)
  • I/O
  • UI rendering (浏览器独有)

微任务microtask,也叫jobs。 另一些异步任务的回调会依次进入micro task queue,等待后续被调用,这些异步任务包括:

  • process.nextTick (Node独有)
  • Promise.then()
  • Object.observe
  • MutationObserver

( 注:这里只针对浏览器和NodeJS )
注意:Promise构造函数里的代码是同步执行的。

浏览器端和 Node 端有什么不同

setTimeout(() => {
	console.log(1)
	Promise.resolve(3).then(res => console.log(res))
}, 0)![在这里插入图片描述](https://img-blog.csdnimg.cn/20190813180310162.png)

setTimeout(() => {
	console.log(2)
}, 0)

// 浏览器: 1 3 2
// node : 1 2 3

浏览器的 Event Loop

一个函数执行栈、一个事件队列和一个微任务队列。

浏览器的Event Loop
每从事件队列中取一个事件时有微任务就把微任务执行完,然后才开始执行事件
在这里插入图片描述

Node 端的 Event Loop

Node 端的 Event Loop
在这里插入图片描述
Node.js的Event Loop过程:

  1. 执行全局Script的同步代码
  2. 执行microtask微任务,先执行所有Next Tick Queue中的所有任务,再执行Other Microtask Queue中的所有任务
  3. 开始执行macrotask宏任务,共6个阶段,从第1个阶段开始执行相应每一个阶段macrotask中的所有任务,注意,这里是所有每个阶段宏任务队列的所有任务,在浏览器的Event Loop中是只取宏队列的第一个任务出来执行,每一个阶段的macrotask任务执行完毕后,开始执行微任务,也就是步骤2
  4. Timers Queue -> 步骤2 -> I/O Queue -> 步骤2 -> Check Queue -> 步骤2 -> Close Callback Queue -> 步骤2 -> Timers Queue …

在这里插入图片描述

总结

  1. 浏览器的Event Loop和Node.js 的Event Loop是不同的,实现机制也不一样。
  2. Node.js 可以理解成有4个宏任务队列和2个微任务队列,但是执行宏任务时有6个阶段。
  3. Node.js 中,先执行全局Script代码,执行完同步代码调用栈清空后,先从微任务队列Next Tick Queue中依次取出所有的任务放入调用栈中执行,再从微任务队列Other Microtask Queue中依次取出所有的任务放入调用栈中执行。然后开始宏任务的6个阶段,每个阶段都将该宏任务队列中的所有任务都取出来执行(注意,这里和浏览器不一样,浏览器只取一个),每个宏任务阶段执行完毕后,开始执行微任务,再开始执行下一阶段宏任务,以此构成事件循环。
  4. MacroTask包括: setTimeout、setInterval、 setImmediate(Node)、requestAnimationFrame(浏览器)、IO、UI rendering
  5. Microtask包括: process.nextTick(Node)、Promise.then、Object.observe、MutationObserver

注意:new Promise() 构造函数里面是同步代码,而非微任务。

细节特性

微任务有两种 nextTick和 then 那么这两个谁快呢?
Promise.resolve('promise').then(res => console.log(res))
process.nextTick(() => console.log('nextTick'))

// nextTick promise
明显是: nextTick
解释:
promise.then 虽然和 process.nextTick 一样,都将回调函数注册到 microtask,但优先级不一样。process.nextTick 的 microtask queue 总是优先于 promise 的 microtask queue 执行。

setTimeout 和 setImmediate

setTimeout 和 setImmediate 执行顺序不固定 取决于node的准备时间

setTimeout(() => {
    console.log('setTimeout')
}, 0)

setImmediate(() => {
    console.log('setImmediate')
})

运行结果:
setImmediate
setTimeout
或者:
setTimeout
setImmediate

为什么结果不确定呢?
解释:
setTimeout/setInterval 的第二个参数取值范围是:[1, 2^31 - 1],如果超过这个范围则会初始化为 1,
即 setTimeout(fn, 0) === setTimeout(fn, 1)。
我们知道 setTimeout 的回调函数在 timer 阶段执行,setImmediate 的回调函数在 check 阶段执行,Event Loop 的开始会先检查 timer 阶段,但是在开始之前到 timer 阶段会消耗一定时间;

所以就会出现两种情况:

  1. timer 前的准备时间超过 1ms,满足 loop->time >= 1,则执行 timer 阶段(setTimeout)的回调函数。
  2. timer 前的准备时间小于 1ms,则先执行 check 阶段(setImmediate)的回调函数,下一次 event loop 执行 timer 阶段(setTimeout)的回调函数。
setTimeout(() => {
    console.log('setTimeout')
}, 0)

setImmediate(() => {
    console.log('setImmediate')
})

const start = Date.now()
while (Date.now() - start < 10);

运行结果一定是:
setTimeout
setImmediate

const fs = require('fs')

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log('setTimeout')
    }, 0)

    setImmediate(() => {
        console.log('setImmediate')
    })
})

运行结果:
setImmediate
setTimeout
解释:
fs.readFile 的回调函数执行完后:
注册 setTimeout 的回调函数到 timer 阶段
注册 setImmediate 的回调函数到 check 阶段
event loop 从 pool 阶段出来继续往下一个阶段执行,恰好是 check 阶段,所以 setImmediate 的回调函数先执行
本次 event loop 结束后,进入下一次 event loop,执行 setTimeout 的回调函数
所以,在 I/O Callbacks 中注册的 setTimeout 和 setImmediate,永远都是 setImmediate 先执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值