js事件循环机制 EventLoop 【浏览器和node】

本文深入探讨JavaScript的事件循环机制,对比浏览器与Node.js环境中事件循环的不同之处,详细讲解宏任务和微任务的工作原理。

js的事件循环机制

经典面试题

 setTimeout(()=>{
    console.log('timer1')
     Promise.resolve().then(function() {
        console.log('promise1')
     })
 }, 0)

setTimeout(()=>{
    console.log('timer2')
    Promise.resolve().then(function() {
       console.log('promise2')
    })
}, 0)
复制代码

这段代码在浏览器里打印的结果是 timer1 promise1 timer2 promise2
而在node中打印的却是 timer1 timer2 promise1 promise2
所以我们来看一下浏览器和node的事件机制的区别

浏览器篇

提到js,就会想到单线程,异步,那么单线程是如何做到异步的呢?概念先行,先要了解下单线程和异步之间的关系。

1.单线程和异步

  • 单线程是指js引擎中负责解析执行js代码的线程只有一个
  • 异步是指在主线程在等待X响应的同时是会去做其它事

2.调用栈和任务队列

顾名思义,调用栈是一个栈结构,函数调用会形成一个栈帧,帧中包含了当前执行函数的参数和局部变量等上下文信息,函数执行完后,它的执行上下文会从栈中弹出。
下图就是调用栈和任务队列的关系图

3.异步任务:宏任务(macrotask)& 微任务(microtask)

从规范理解,浏览器至少有一个事件循环,一个事件循环至少有一个任务队列(macrotask),每个外任务都有自己的分组,浏览器会为不同的任务组设置优先级。

宏任务:script代码执行、setInterval、 setTimeout setImmediateMessageChannel (常见的)
微任务:Promise.then、 MutaionObserver(有兼容问题)、process.nextTick(Node.js环境)

其中setImmediate和process.nextTick是nodejs的实现,在nodejs篇会详细介绍。 下图就是补充上的宏任务和微任务的事件机制

总结起来,一次事件循环的步骤包括:

  1. 首先执行同步任务
  2. 遇到异步任务,按照宏任务和微任务分别放置。
  3. 执行微任务,并清空本次循环的微任务队列。
  4. 执行排在第一个的宏任务。
  5. 再次查看微任务队列,形成循环。

这是补上宏任务和微任务的循环图。

请看练习题

Promise.resolve().then(function() {
    console.log('promise3')
    setTimeout(() => {
        console.log('timer1')

        Promise.resolve().then(function() {
            console.log('promise1')
        })
    }, 100)

})
setTimeout(() => {
    console.log('timer2')

    Promise.resolve().then(function() {
        setTimeout(() => {
            console.log('time3')
        }, 100)
        console.log('promise2')
    })
}, 0) 

复制代码

我们来分析一下: 1.首先读读取代码,微任务promise3 放入微任务队列,宏任务setTimeouttimer2放入宏任务队列。
2.先执行微任务,打印微任务promise3,读取setTimeout timer1,在100秒后将其放入宏任务队列,并在timer2 后面。
3.执行宏任务,打印timer2。并执行微任务,打印promise2,并执行后面的代码,100秒后将time3放入宏任务。

Promise.resolve().then(function() {
        setTimeout(() => {
            console.log('time3')
        }, 100)
        console.log('promise2')
    })

复制代码

4.100秒后执行打印timer1,执行微任务 打印promise1 5.执行下一个宏任务 打印time3 答案就是promise3 timer2 promise2 timer1 promise1 time3

node篇

node是什么

Node.js是一个基于 Chrome V8 引擎的JavaScript运行环境(runtime); Node不是一门语言是让js运行在后端的运行时。

node可以做什么

Node在处理高并发,I/O密集场景有明显的性能优势
高并发,是指在同一时间并发访问服务器
I/O密集指的是文件操作、网络操作、数据库,相对的有CPU密集,
CPU密集指的是逻辑处理运算、压缩、解压、加密、解密

node可以做什么

当应用程序需要处理大量并发的输入输出,而在向客户端响应之前,应用程序并不需要进行非常复杂的处理。

  • 聊天服务器
  • 电子商务网站

node是怎么工作的

1.我们写的js代码会交给v8引擎进行处理
2.代码中可能会调用nodeApi,node会交给libuv库处理
3.libuv通过阻塞i/o和多线程实现了异步io
4.通过事件驱动的方式,将结果放到事件队列中,最终交给我们的应用。

  • 在libuv内部有这样一个事件环机制。 在node启动时会初始化事件环。 事件循环会无限次地执行,一轮又一轮。 只有异步任务的回调函数队列清空了,才 会停止执行。每一轮的事件循环,分成六 个阶段。这些阶段会依次执行。
  • 为了协调异步任务,Node 提供了四个定时器, 让任务可以在指定的时间运行。
    -- setTimeout()
    -- setInterval()
    -- setImmediate()
    -- process.nextTick()【微任务】

回顾浏览器环境下,microtask的任务队列 是每个macrotask执行完之后执行
而在Node.js中,microtask会在事件循环的 各个阶段之间执行,也就是一个阶段执行完毕, 就会去执行microtask队列的任务。并且process.nextTick()会优先执行

timers 阶段

这个是定时器阶段,处理setTimeout()和setInterval()的回调函数。 进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。 如果满足就执行回调函数,否则就离开这个阶段。

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

setImmediate(() => { console.log('immediate') }) // 执行时间不确定。

1.setTimeout在 timers 阶段执行,而setImmediate在 check 阶段执行,所以是timeout/immediate

2.setTimeout的第二个参数默认为0,但是实际上,Node 做不到0毫秒,最少也需要1毫秒,根据官方文档, 第二个参数的取值范围在1毫秒到2147483647毫秒之间。也就是说,setTimeout(f, 0)等同于setTimeout(f, 1)。 实际执行的时候,进入事件循环以后,有可能到了1毫秒,也可能还没到1毫秒,取决于系统当时的状况, 如果没到1毫秒,那么 timers 阶段就会跳过,进入 check 阶段,先执行setImmediate的回调函数。

poll阶段

这个阶段是轮询时间,用于等待还未返回的 I/O 事件, 比如服务器的回应、用户移动鼠标等等。 这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器), 会一直停留在这个阶段,等待 I/O 请求返回结果

check 阶段

执行setImmediate

最一道测试题,如果能全部回答正确 说明你就掌握了事件机制了哦!

let fs = require('fs');
fs.readFile('./1.txt', function(err, data) {
   console.log('1读取文件')
});
setTimeout(() => {
   console.log('2')

}, 0)
console.log('3')
Promise.resolve().then(function() {
   console.log('4')
   setTimeout(() => {
       console.log('5')

       Promise.resolve().then(function() {
           console.log('6')
       })
   }, 300)

})
setTimeout(() => {
   console.log('7')

}, 300)
process.nextTick(() => console.log(8));
//3,8,4,2,1,7,5,6

复制代码
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值