事件循环机制Event Loop
js是一种单线程非阻塞式的解释型脚本语言,单线程意味着一次只能执行一个任务,那么当一个任务在执行时,其他的任务就需要排队等待,他们的执行顺序是怎样的呢?
任务主要分为同步任务和异步任务两种,当我们的程序里面既有同步任务又有异步任务存在的时候应该怎么去执行呢?我们来看下面这一段代码
console.log('aaa');
setTimeout(()=>{
console.log('bbb')
},2000);
console.log('ccc')
如果按照代码从上到下的顺序来执行,第一个被打印的应该是aaa,然后是我们的定时器2s后输出bbb,最后在输出ccc,但是运行结果真的是这样么?
控制台的输出顺序为aaa,ccc,bbb,为什么呢?因为我们的setTimeout是异步代码,排在异步任务后面的代码,不用等待异步任务结束会马上运行,也就是说,异步任务不具有”堵塞“效应。再来看下面这段代码:
setTimeout(function(){
console.log('4定时器开始啦')
});
new Promise(function(resolve){
console.log('1马上执行for循环啦');
for(var i = 0; i < 10000; i++){
i == 99 && resolve();
}
}).then(function(){
console.log('3执行then函数啦')
});
console.log('2代码执行结束');
执行结果:
// '1马上执行for循环啦'
// '2代码执行结束'
// '3执行then函数啦'
// '4定时器开始啦'
为什么执行顺序是这样的呢?这里我们要提出一个概念Event Loop。我们已经知道js的任务里面有同步任务和异步任务,而在异步任务里面又分为宏任务和微任务。上述代码中的setTimeout就是宏任务,而promise.thenk则是微任务。加上我们的同步任务总共有三种不同的任务类型,也分别对应三个处理队列:
- 主线程——就是访问到的script标签里面包含的内容,或者是直接访问某一个js文件的时候,里面的可以在当前作用域直接执行的所有内容(执行的方法,new出来的对象等)。所有同步代码都在主线程中执行。
- 宏队列(macrotask)——宏任务:setTimeout、setInterval、setImmediate、I/O、UI rendering
- 微队列(microtask)——微任务:promise.then、process.nextTick
它们执行顺序又是怎么样的呢?
- 先执行主线程,从上到下执行所有同步代码。
- 遇到宏任务放到宏任务队列,遇到微任务放到微任务队列。
- 主线程执行完毕后检查微任务队列,将可执行微任务全部执行。
- 取出宏任务中第一项执行。
- 再回到第三布重复执行。
需要注意的是,promise.then是微任务,而promise只是同步任务
有了上诉的理论,我们再来解决下面的题目
console.log(1)
setTimeout(function() {
console.log(5);
})
Promise.resolve().then(function() {
console.log('promise3')
}).then(function() {
console.log('promise4')
})
console.log(2);
// 1
// 2
// 'promise3'
// 'promise4'
// 5
console.log(1)
setTimeout(() => {
console.log(5)
})
new Promise((resolve, reject) => {
console.log(2);
setTimeout(() => {
console.log(4);
resolve()
}, 6000)
}).then(() => {
console.log('promise5')
})
console.log(3);
// 1
// 2
// 3
// 5
// 4
// 'promise5'
setTimeout(() => {
console.log('3setTimeout');
})
new Promise((resolve) => {
console.log('1promise');
setTimeout(() => {
resolve()
}, 3000)
}).then(() => {
console.log('4then');
})
console.log('2console');
// '1promise'
// '2console'
// '3setTimeout'
// '4then'
上面这段代码需要大家注意,首先将第一个setTimeout加入宏队列,然后执行promis里面的1promise和最后一句同步代码2console,但是由于在promise里面并没有马上resolve,所以此时promise.then并没有触发,也就没有加入微任务,此时微任务是空的,然后遇到了promise里面写的setTimeout,加入宏任务队列,由于此时微队列为空,固执行第一个宏任务3setTimeout,然后检查微队列,此时依然为空,然后再执行promise里面的setTimeout,此时调用了resolve,将promise.then加入微队列,然后执行。
console.log('1111');
setTimeout(() => {
console.log('4444');
new Promise((resolve) => {
console.log('5555');
resolve();
}).then(() => {
console.log('6666')
})
})
new Promise((resolve) => {
console.log('2222');
resolve();
}).then(() => {
console.log('3333')
})
setTimeout(() => {
console.log('7777');
new Promise((resolve) => {
console.log('8888');
resolve();
}).then(() => {
console.log('9999')
})
});
// '1111'
// '2222'
// '3333'
// '4444'
// '5555'
// '6666'
// '7777'
// '8888'
// '9999'