浏览器EventLoop执行过程解析

EventLoop又叫事件循环,用来控制浏览器中事件的执行过程。

浏览器JS执行过程

如下图所示,浏览器存在执行栈(单线程执行JS代码)、任务队列及一些WEB API。

在浏览器中运行如下代码:

console.log(1);

setTimeout(function(){
    console.log(2);
}, 5000);

console.log(3);

// 输出:
// 1
// 3
// 2
复制代码

执行过程如下(演示地址):

  1. 执行console.log,输出1
  2. 执行setTimeout,启动定时器
  3. 执行console.log,输出3
  4. 定时器到达时间,把回调放入任务队列
  5. 执行栈是空的,从任务队列取任务,放入执行栈
  6. 执行回调中的console.log,输出2

理解了这个执行过程,就能明白为什么下面代码(setTimeout时间设为0)的输出结果与上面相同:

console.log(1);

setTimeout(function(){
    console.log(2);
}, 0);

console.log(3);

// 输出:
// 1
// 3
// 2
复制代码

执行过程:

  1. 执行console.log,输出1
  2. 执行setTimeout,启动定时器
  3. 定时器到达时间,把回调放入任务队列
  4. 执行栈非空,继续执行console.log,输出3
  5. 执行栈为空,从任务队列取任务,放入执行栈
  6. 执行回调中的console.log,输出2

宏任务/微任务

并不是所有异步任务的执行优先级都相同,微任务(microtask)比宏任务(macrotask)要优先执行。

在浏览器环境中,常见的宏任务有setTimeoutMessageChannelpostMessagesetImmediate;常见的微任务有MutationObseverPromise.then

从下面这段代码来看浏览器的执行过程:

setTimeout(() => {
    console.log('timeout1');
    Promise.resolve().then(() => {
        console.log('promise1');
    });
    Promise.resolve().then(() => {
        console.log('promise2');
    });
}, 0);

setTimeout(() => {
    console.log('timeout2');
    Promise.resolve().then(() => {
        console.log('promise3')
    });
}, 0);

// 输出:
// timeout1
// promise1
// promise2
// timeout2
// promise3
复制代码

以下为详细执行过程:

  • 开始
    • 执行栈:setTimeout,setTimeout
    • 微任务队列:
    • 宏任务队列:
    • 输出:
  • 执行第一个setTimeout,启动定时器1
    • 执行栈:setTimeout
    • 微任务队列:
    • 宏任务队列:
    • 输出:
  • 定时器1时间到,将回调T1放入宏任务队列
    • 执行栈:setTimeout
    • 微任务队列:
    • 宏任务队列:回调T1
    • 输出:
  • 执行setTimeout,启动定时器2
    • 执行栈:
    • 微任务队列:
    • 宏任务队列:回调T1
    • 输出:
  • 定时器2时间到,将回调T2放入宏任务队列
    • 执行栈:
    • 微任务队列:
    • 宏任务队列:回调T1,回调T2
    • 输出:
  • 执行栈为空,微任务队列为空,从宏任务队列中取出回调T1,放入执行栈
    • 执行栈:console.log,Promise.then,Promise.then
    • 微任务队列:
    • 宏任务队列:回调T2
    • 输出:
  • 执行console.log,输出timeout1
    • 执行栈:Promise.then,Promise.then
    • 微任务队列:
    • 宏任务队列:回调T2
    • 输出:timeout1
  • 执行Promise.then,将回调P1放入微任务队列
    • 执行栈:Promise.then
    • 微任务队列:回调P1
    • 宏任务队列:回调T2
    • 输出:timeout1
  • 执行下一个Promise.then,将回调P2放入微任务队列
    • 执行栈:
    • 微任务队列:回调P1,回调P2
    • 宏任务队列:回调T2
    • 输出:timeout1
  • 执行栈为空,从微任务队列取出回调P1,放入执行栈
    • 执行栈:console.log
    • 微任务队列:回调P2
    • 宏任务队列:回调T2
    • 输出:timeout1
  • 执行console.log,输出promise1
    • 执行栈:
    • 微任务队列:回调P2
    • 宏任务队列:回调T2
    • 输出:timeout1,promise1
  • 执行栈为空,从微任务队列取出回调P2,放入执行栈
    • 执行栈:console.log
    • 微任务队列:
    • 宏任务队列:回调T2
    • 输出:timeout1,promise1
  • 执行console.log,输出promise2
    • 执行栈:
    • 微任务队列:
    • 宏任务队列:回调T2
    • 输出:timeout1,promise1,promise2
  • 执行栈为空,微任务队列为空,从宏任务队列取出回调T2,放入执行栈
    • 执行栈:console.log,Promise.then
    • 微任务队列:
    • 宏任务队列:
    • 输出:timeout1,promise1,promise2
  • 执行console.log,输出timeout2
    • 执行栈:Promise.then
    • 微任务队列:
    • 宏任务队列:
    • 输出:timeout1,promise1,promise2,timeout2
  • 执行Promise.then,将回调P3放入微任务队列
    • 执行栈:
    • 微任务队列:回调P3
    • 宏任务队列:
    • 输出:timeout1,promise1,promise2,timeout2
  • 任务队列为空,从微任务队列取出回调P3,放入执行栈
    • 执行栈:console.log
    • 微任务队列:
    • 宏任务队列:
    • 输出:timeout1,promise1,promise2,timeout2
  • 执行console.log,输出promise3
    • 执行栈:
    • 微任务队列:
    • 宏任务队列:
    • 输出:timeout1,promise1,promise2,timeout2,promise3
  • 完成
    • 执行栈:
    • 微任务队列:
    • 宏任务队列:
    • 输出:timeout1,promise1,promise2,timeout2,promise3

参考资料:

浅谈js运行机制(线程)

理解 JavaScript 中的 macrotask 和 microtask

转载于:https://juejin.im/post/5b6ad8d06fb9a04fa5610d59

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值