JS的事件循环机制EventLoop

部分的内容出处:面试题:说说事件循环机制(满分答案来了)
 

浏览器中的事件循环

JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

macro-task大概包括:

  • script(整体代码)
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI render

micro-task大概包括:

  • process.nextTick
  • Promise
  • Async/Await(实际就是promise)
  • MutationObserver(html5新特性)

例子1:

setTimeout(_ => console.log(1))

new Promise(resolve => {
  resolve()
  console.log(2)
}).then(_ => {
  console.log(3)
})

console.log(4)

执行结果:

2
4
3
1

运行过程:

1、将宏任务(setTimeout的回调事件)推进宏任务队列( event queue )
2、立即执行new Promise 打印:2 并将.then内的回调事件推进微任务队列
3、console.log(4)由于处于主线程, 因此直接打印:4
4、检查微任务队列中是否还有微任务未执行,发现有1个.then的回调事件未执行,因此执行打印:3
5、执行宏任务的回调事件, 打印:  1

列子2:

console.log('sync statement 1');        // 1、处于主线程, 直接执行
Promise.resolve().then(function() {     // 2、将.then微任务推进微任务队列
    console.log('micro task 1');        // 5、执行微任务, 并将之后的.then推进微队列
    setTimeout(function() {             // 6、将回调推进宏任务队列
        console.log('macro task 1');    // 10、执行宏队列
    }, 0);
}).then(function() {                    
    console.log('micro task 2');        // 7、执行微队列
});

setTimeout(function() {                 // 3、将宏任务推进宏任务队列
    console.log('macro task 2')         // 7、执行宏队列
    Promise.resolve().then(function(){  // 8、将微任务推进微队列
        console.log('micro task 3');    // 9、由于当前宏任务下的微任务只有.then的回调事件,因此执行
    })
}, 0)

console.log('sync statement 2');        // 4、处于主线程,直接执行 
执行结果: 
        'sync statement 1'
        'sync statement 2'
        'micro task 1'
        'micro task 2'
        'macro task 2'
        'micro task 3'
        'macro task 1'

运行过程示意图:

 

例子3:

//主线程直接执行
console.log('1');
//丢到宏事件队列中
setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
//微事件1
process.nextTick(function() {
    console.log('6');
})
//主线程直接执行
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    //微事件2
    console.log('8')
})
//丢到宏事件队列中
setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})

运行结果:

执行结果:
        1 7 6 8 2 4 3 5 9 11 10 12

执行过程:

    1、由于console.log('1')处于主线程, 因此直接执行 打印: 1
    2、将宏任务setTimeout的回调事件推入宏任务队列, 待微任务队列执行完成后再取出
    3、将微任务process.nextTick的回调事件推入微任务队列
    4、直接执行new Promise, 打印: 7, 并将微任务.then的回调事件推进微任务队列等待后续取出执行
    5、执行setTimeout, 并将宏任务推入宏任务队列
    6、检查微任务队列中是否还有微任务未执行, 由以上几点可知微任务队列有process.nextTick、.then对应的回调事件, 根据事件的先进先出原则, 取出并执行, 分别打印出: 6、8
    7、此时微任务队列里的任务已经执行完毕, 开始取出宏任务队列中的宏任务
    8、根据事件的先进先出原则, 先执行第一个setTimeout的回调事件, 打印: 2, 将微任务process.nextTick的回调事件推进微任务队列, 再执行new Promise, 打印4, 并将微任务.then也推进微任务队列。
    9、然后检查微任务队列中是否还有未执行的微任务, 由上可知有两个,分别是process.nextTick、.then对应的回调事件, 取出并分别先后执行, 打印: 3、4
    10、当前的宏任务产生的微任务已经全部执行完毕, 因此继续从宏任务队列中取出未执行的宏任务,由上我们可知还有一个宏任务, 也就是第二个setTimeout
    11、取出第二个setTimeout的回调事件,执行过程同步骤8、9和10相同, 先后打印出: 9、11、10、12

运行过程的示意图:

 

例子4:

console.log('start')
setTimeout(() => {
    console.log('定时器...')
}, 0);
new Promise((resolve)=>{
    resolve();
    console.log('new Promise');
}).then(()=>{
    console.log('...then')
})
function sync(){
    console.log('同步代码')
}
async function async2(){
    new Promise(resolve=>{
        resolve()
    }).then(()=>{
        console.log('async2...then')
    })
}
async function async1(){
    await async2();
    console.log('继续执行了吗')
}
sync();
async1();
console.log('end')

运行结果:

打印结果:
   'start' 
    'new Promise'
    '同步代码'
    'end'
    '...then'
    'async2...then'
    '继续执行了吗'
    '定时器...'

执行过程示意图:

例子5:

async function async1() {
    console.log('async1 start');
    await async2();
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout1')
    },0)
}
async function async2() {
    //更改如下:
    setTimeout(function() {
        console.log('setTimeout2')
    },0)
}
console.log('script start');

setTimeout(function() {
    console.log('setTimeout3');
}, 0)
async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

运行结果:

'script start'
'async1 start'
'promise1'
'script end'
'promise2'
'setTimeout3'
'setTimeout2'
'setTimeout1'

运行过程示意图:

例子6:

new Promise(resolve => {
    resolve(1);
    Promise.resolve().then(() => {
        console.log(2)
    });
    console.log(4)
}).then(t => {
    console.log(t)
});
console.log(3); 

运行结果:

    运行结果: 
        4
        3
        2
        1

运行过程:

运行过程: 
        1、执行new Promise(), 执行resolve(1)抛出返回值1, 继续执行微任务Promise.resolve().then(), 将.then的回调事件推入微任务队列, 继续执行console.log(4), 打印: 4, 执行到new Promise的.then, 将.then的微任务回调事件推入到微任务队列中
        2、继续执行处于主线程的console.log(3), 打印: 3
        3、检查微任务队列是否还有未执行的微任务, 由上可知有两个: 分别是: console.log(2)、console.log(t), 其中t的值由resolve(1)传入, 分别先后取出执行, 先后打印2、1

运行过程示意图:

例子7:

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

console.log('script start');

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

async1();

new Promise(function(resolve) {
    console.log('promise1');
    resolve();
}).then(function() {
    console.log('promise2');
});
console.log('script end');

运行结果:

运行结果:
        'script start'
        'async1 start'
        'async2'
        'promise1'
        'script end'
        'async1 end'
        'promise2'
        'setTimeout'

运行过程:

运行过程:
    1、处于主线程的console.log('script start')直接运行, 打印出: 'script start'
    2、将setTimeout对应的回调事件推入宏任务队列
    3、执行async1(), 打印: 'async1 start', 然后执行await async2(), 打印: async2, 这里要注意: 执行await后的语句async2()后会立即跳出async1, 不会继续执行接下来的console.log('async1 end'); 这是因为执行await后会抛出一个fulfilled(成功)状态的promise对象, 后续的执行代码console.log('async1 end')就等同于.then里的回调事件, 因此console.log('async1 end')会被推入到微任务队列等待执行
    4、跳出async1后继续执行new Promise(), 打印: 'promise1', 并将.then的回调事件推入到微任务队列中等待执行
    5、继续执行console.log('script end'); 打印: 'script end', 
    6、检查微任务队列中是否还有未执行的微任务, 由上可知微任务队列中还有: async1
    中的console.log('async1 end')、new Promise中.then的console.log('promise2')这两个事件未被执行, 根据先进先出的执行原则, 分别执行打印的结果是: 'async1 end'、'promise2'
    7、此时微任务队列中任务已经执行完毕, 开始检查宏任务队列中是否还有宏任务未执行, 由上可知还有一个setTimeout产生的宏任务事件, 取出并执行, 打印: 'setTimeout'

以上执行过程的描述是我个人对事件循环机制的理解做出的,如有不足或错误地方,希望多多指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值