JS的事件循环机制

JS的事件循环机制

1. 浏览器的事件循环机制

1.1 事件执行的顺序
  1. 当事件开始时,首先会进入JS主线程机制,由于JS属于单线程机制,因此存在多个任务的时候会存在等待的情况,先等待最先进入线程的事件处理完毕

  2. 这样就会出现等待的情况,如果之前的事件没有执行完成,后面的事件就会一直等待

  3. 但是类似于AJAXsetTimeout , setInterval 等待的事件,就出现了异步处理

  4. 通过将异步的事件交给异步模块处理,主线程就会去并行的处理后面的事件

  5. 当主线程空闲的时候,异步处理完成,主线程就会读取异步处理返回的callback执行异步事件后续的操作

同步执行就是主线程按照事件的顺序,依次执行事件
异步执行就是主线程先跳过等待,执行后面的事件,异步事件交给异步模块处理
图解

在这里插入图片描述

  1. 事件开始,进入主线程
  2. 主线程如果发现异步事件,将异步事件移交给异步模块,主线程继续执行后面的同步事件
  3. 当异步进程执行完毕之后,再回到事件队列中
  4. 主线程执行完毕,就会查询事件队列,如果事件队列中存在事件,事件队列就将事件推到主线程
  5. 主线程执行事件队列推过来的事件,执行完毕后再去事件队列中查询… 这个循环的过程就是事件循环

1.2 异步任务又分为宏任务(macrotask)和微任务(microtask)

常见的宏任务:setTimeout setInterval I/O script

常见的微任务:promise

同一事件循环中,微任务永远在宏任务之前

同一事件循环中,微任务永远在宏任务之前

同一事件循环中,微任务永远在宏任务之前

小栗子1

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

Promise.resolve().then(()=> {
    console.log(3)
})
console.log(4)

// 结果 1 4 3 2
  1. 任务开始执行,进入执行栈。遇到console.log(1),是同步任务,输出1

  2. 执行栈继续执行,遇到定时器setTimeout,是异步宏任务,进入异步进程的宏任务队列;

  3. 执行栈继续执行,遇到Promise,是异步微任务,进入异步进程的微任务队列;

  4. 执行栈继续执行,遇到console.log(4),是同步任务,输出4

  5. 至此同步任务执行完成,开始查询任务队列,微任务队列在宏任务队列之前,先执行微任务队列输出3

  6. 微任务队列执行完毕,再查询宏任务队列,输出2,至此整个任务队列完成,最后输出1 4 3 2

小栗子2

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

new Promise((resolve) => {
    console.log(3)
    resolve(4)
}).then((res)=> {
    console.log(res)
})
console.log(5)

// 最后结果 																				     1 3 5 4 2

大栗子3

setTimeout(() => console.log('setTimeout1'), 0) //1宏任务
setTimeout(() => {								//2宏任务
    console.log('setTimeout2')
    Promise.resolve().then(() => {
        console.log('promise3')
        Promise.resolve().then(() => {
            console.log('promise4')
        })
        console.log(5)
    })
    setTimeout(() => console.log('setTimeout4'), 0)  //4宏任务
}, 0)
setTimeout(() => console.log('setTimeout3'), 0)  //3宏任务
Promise.resolve().then(() => {//1微任务
    console.log('promise1')
})

// 最后结果
/**
promise1
setTimeout1
setTimeout2
promise3
5
promise4
setTimeout3
setTimeout4
*/

1.3 遇到asyncawait的情况

一旦遇到await 就立刻让出线程,阻塞后面的代码,先执行async外面的同步代码

等候之后,对于await来说分两种情况:不是promise 对象;是promise对象

  1. 如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果
  2. 如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
  3. 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。

----栗子(没有 async await)代码依次执行

function fn1() {
    return 1
}
function fn2 () {
    console.log(2)
    console.log(fn1())
    console.log(3)
}
fn2()
console.log(4)
// 结果 2 1 3 4 

----栗子(没有promise)如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果

async function fn1() {
    return 1
}
async function fn2 () {
    console.log(2)
    console.log(await fn1())
    console.log(3)
}
fn2()
console.log(4)
// 结果 2 4 1 3

—栗子(存在promise) 如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。

function fn1 () {
    return new Promise((reslove) => {
        reslove(1)
    })
}
async function fn2() {
    console.log(2)
    console.log(await fn1())
    console.log(3)
}
fn2()
console.log(4)

// 结果 2 4 1 3

2. NodeJS的事件循环

NodeJS相对于浏览器的事件循环多了一个微任务process.nextTick()和宏任务setImmediate()

   ┌───────────────────────────┐
┌─>│           timers          │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │     pending callbacks     │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
│  │       idle, prepare       │
│  └─────────────┬─────────────┘      ┌───────────────┐
│  ┌─────────────┴─────────────┐      │   incoming:   │
│  │           poll            │<─────┤  connections, │
│  └─────────────┬─────────────┘      │   data, etc.  │
│  ┌─────────────┴─────────────┐      └───────────────┘
│  │           check           │
│  └─────────────┬─────────────┘
│  ┌─────────────┴─────────────┐
└──┤      close callbacks      │
   └───────────────────────────┘
  1. timers 阶段: 这个阶段执行 setTimeout(callback)setInterval(callback) 预定的 callback;

  2. I/O callbacks 阶段: 此阶段执行某些系统操作的回调,例如TCP错误的类型。 例如,如果TCP套接字在尝试连接时收到 ECONNREFUSED,则某些* nix系统希望等待报告错误。 这将操作将等待在I/O回调阶段执行;

  3. idle, prepare 阶段: 仅node内部使用;

  4. poll 阶段: 获取新的I/O事件, 例如操作读取文件等等,适当的条件下node将阻塞在这里;

  5. check 阶段: 执行 setImmediate() 设定的callbacks;

  6. close callbacks 阶段: 比如 socket.on(‘close’, callback) 的callback会在这个阶段执行;

process.nextTick()
process.nextTick(() => {
  //做些事情
})

每当事件循环进行一次完整的行程时,我们都将其称为一个滴答。

当将一个函数传给 process.nextTick() 时,则指示引擎在当前操作结束(在下一个事件循环滴答开始之前)时调用此函数

传给 process.nextTick() 的函数会在事件循环的当前迭代中(当前操作结束之后)被执行。 这意味着它会始终在 setTimeoutsetImmediate 之前执行。

相比于Promiseprocess.nextTick()会先执行

Promise.resolve().then(() => {
    console.log('promise')
})
process.nextTick(()=> {
    console.log('process')
})
// 运行结果
/*
process
promise
*/
setImmediate()
setImmediate(() => {
  //运行一些东西
})

作为 setImmediate() 参数传入的任何函数都是在事件循环的下一个迭代中执行的回调

延迟 0 毫秒的 setTimeout() 回调与 setImmediate() 非常相似。 执行顺序取决于各种因素,但是它们都会在事件循环的下一个迭代中运行。

参考链接

事件循环机制Event loop
javascript宏任务和微任务

  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值