JavaScript | 事件循环机制

JavaScript 是一种单线程语言,依赖于 事件循环机制 (以下简称 Event Loop),完成对同步和异步任务的处理,从而实现非阻塞并发。这里将从基础概念到实际例子,进一步解释事件循环的运作机制。

一、基础概念

事件循环是一个运行中的机制,用于调度调用堆栈 (Call Stack)、任务队列 (Task Queue),以及各种存储并执行任务的空闲时机。它是完成异步操作的核心组成。

1. 调用堆栈/执行栈

执行栈是一种**后进先出 (LIFO)**类型的数据结构,用于记录正在执行和待执行的函数。

如果有新的函数被调用,将会被添加到调用堆栈的顶部;当函数执行完成时,会从堆栈顶部删除。

2.任务队列

任务队列包括宏任务队列和微任务队列

常见任务分类
  • 宏任务 (Macro-task): script脚本的执行,setTimeout ,setInterval ,I/O ,UI渲染等
  • 微任务 (Micro-task): Promise的回调 ,对DOM变化坚挺的MutationObserver,queueMicrotask,等

二、事件循环机制

因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。

Event Loop 执行顺序如下所示:
  • 首先执行同步代码,这属于宏任务
  • 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
  • 执行所有微任务
  • 当执行完所有微任务后,如有必要会渲染页面
  • 然后开始下一轮 Event Loop,执行宏任务中的异步代码

三、例题

1.判断输出结果

const promise = new Promise((resolve, reject) => {
  console.log(1);
  console.log(2);
});
promise.then(() => {
  console.log(3);
});
console.log(4);

// 输出结果如下:
1  2  4
  • Promise.then是微任务,它会在所有的宏任务执行完之后执行,同时需要promise内部的状态发生变化,因为这里内部没有发生变化,一直处于pending状态,所以不输出3

2. 判断输出结果

const promise1 = new Promise((resolve, reject) => {
  console.log('promise1')
  resolve('resolve1')
})
const promise2 = promise1.then(res => {
  console.log(res)
})
console.log('1', promise1);
console.log('2', promise2);

// 输出结果如下:
promise1
1 Promise{<resolved>: resolve1}
2 Promise{<pending>}
resolve1
  • 注意:直接打印promise1,会打印出它的状态值和参数
  • 代码执行过程如下:
    • script是一个宏任务,按照顺序执行代码
    • 首先进入Promise,执行该构造函数中的代码,打印promise1
    • 碰到resolve函数,将promise1的状态改变为resolved,并将结果保存下来
    • 碰到promise1.then这个微任务,将它放入微任务队列
    • promise2是一个新的状态为pending的promise
    • 执行同步代码1,同时打印promise的状态resolved
    • 执行同步代码2,同时打印promise2的状态pending
    • 宏任务执行完毕,查找微任务队列,发现promise1.hten这个微任务且状态为resolved

3. 判断输出结果

Promise.resolve().then(() => {
  console.log('promise1');
  const timer2 = setTimeout(() => {
    console.log('timer2')
  }, 0)
});
const timer1 = setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(() => {
    console.log('promise2')
  })
}, 0)
console.log('start');

// 输出结果如下:
start  promise1  timer1  promise2  timer2
  • 代码执行过程如下:
    • 首先,Promise.resolve().then()是一个微任务,加入微任务队列
    • 执行timer1,它是一个宏任务,加入宏任务队列
    • 继续执行下面的同步代码,输出start
    • 这样第一轮宏任务就执行完了,开始执行微任务Promise.resolve().then,输出promise1
    • 遇到宏任务timer2,将其加入宏任务队列,此时宏任务队列有两个任务,分别是timer1,timer2
    • 这样第一轮微任务就执行完了,开始执行第二轮宏任务,首先执行定时器timer1,输出timer1
    • 遇到微任务Promise.resolve().then(),加入微任务队列
    • 此时微任务队列不为空,执行微任务队列中的任务,输出promise2
    • 最后执行宏任务队列中的timer2,输出timer2

4. 判断输出结果

const promise1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('success')
  }, 1000)
})
const promise2 = promise1.then(() => {
  throw new Error('error!!!')
})
console.log('promise1', promise1)
console.log('promise2', promise2)
setTimeout(() => {
  console.log('promise1', promise1)
  console.log('promise2', promise2)
}, 2000)

// 输出结果如下:
promise1 Promise{<pending>}
promise2 Promise{<pending>}
Uncaught (in promise) Error: error!!!
promise1 Promise{<fulfilled>: "success"}
promsie2 Promise{<rejected>: Error: error!!!}   // 手动抛出Error,因此promise2会被reject,并且会有一个未捕获的错误

5. 判断输出结果

async function async1(){
    console.log("async1 start");
    await async2();
    console.log("async1 end");
}
async function async2(){
    console.log("async2");
}
async1();
console.log("start");

// 输出结果:
async1 start
async2
start
async1 end

代码执行过程如下:

  • 首先执行函数中的同步代码async1 start,之后遇到了await,它会阻塞async1后面代码的执行,因此会去先执行async2中的同步代码async2,然后跳出async1
  • 跳出async1函数后,执行同步代码start
  • 在一轮宏任务全部执行完后,再来执行await后面的内容async1 end

6. 判断输出结果

function runAsync(x){
    const p = new Promise(r=>setTimeout(()=>r(x,console.log(x)),1000));
    return p;
}
function runReject(x){
    const p = new Promise((res,rej)=>setTimeout(()=>rej(`Error:${x}`),1000*x));
    return p;
}

Promise.all([runAsync(1),runReject(4),runAsync(3),runReject(2)])
  .then(res=>console.log(res))
  .catch(err=>console.log(err))
  
  // 输出结果:
  1 3   // 1s后输出
  2 Error:2   // 2s后输出
  4   // 4s后输出
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值