JS的事件循环机制和异步任务的讲解

我们说到JS的事件循环机制同时不得不提到浏览器的事件循环机制

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

1.1 事件执行的顺序

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

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

(3),但是类似于AJAX和setTimeout , 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;
4,微任务队列执行完毕,再查询宏任务队列,输出2,至此整个任务队列完成,最后输出1 4 3 2

案列:2

 setTimeout(function(){
     console.log('定时器开始啦')
 });
 
 new Promise(function(resolve){
     console.log('马上执行for循环啦');
     for(var i = 0; i < 10000; i++){
         i == 99 && resolve();
     }
 }).then(function(){
     console.log('执行then函数啦')
 });
 
 console.log('代码执行结束');
 结果:马上执行for循环啦 --- 代码执行结束 --- 执行then函数啦 --- 定时器开始啦

1,首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里
2,遇到 new Promise直接执行,打印"马上执行for循环啦"
3,遇到then方法,是微任务,将其放到微任务的【队列里】
4,打印 “代码执行结束”
5,本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"执行then函数啦"
到此,本轮的event loop 全部完成。
6,下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"定时器开始啦"

案列:3

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

案列:4

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 遇到async和await的情况

一旦遇到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 正常处理完成并返回其处理结果。

下面是实际遇到的案列仅供参考:

案列:1 (没有 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 

案列:2(没有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

案列: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

案列: 4

async function async1() {
    console.log( 'async1 start' )
    await async2()
    console.log( 'async1 end' )
}
async function async2() {
    console.log( 'async2' )
}
async1()
console.log( 'script start' )
// 结果:执行结果
async1 start
async2
script start
async1 end

一旦遇到await 就立刻让出线程,阻塞后面的代码
等候之后,对于await来说分两种情况
不是promise 对象
是promise对象
如果不是promise,await会阻塞后面的代码,先执行async外面的同步代码,同步代码执行完毕后,在回到async内部,把promise的东西,作为await表达式的结果
如果它等到的是一个 promise 对象,await 也会暂停async后面的代码,先执行async外面的同步代码,等着 Promise 对象 fulfilled,然后把 resolve 的参数作为 await 表达式的运算结果。
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。
1.4 综合上面的案列我们大概知道了异步任务执行顺序了。
操练试一试:

async function t1 () {
  console.log(1)
  console.log(2)
  await new Promise(resolve => {
    setTimeout(() => {
      console.log('t1p')
      resolve()
    }, 1000)
  })
  await console.log(3)
  console.log(4)
}

async function t2() {
  console.log(5)
  console.log(6)
  await Promise.resolve().then(() => console.log('t2p'))
  console.log(7)
  console.log(8)
}

t1()
t2()

console.log('end')

// 输出:
// 1
// 2
// 5
// 6
// end
// t2p
// 7
// 8
// undefined
// t1p
// 3
// 4
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值