javascript中的事件循环(Event Loop)
什么是事件循环(Event Loop)?
首先我们要知道javascript是单线程运行的,遇到大的外部文件时,会阻塞下面程序的运行,造成用户体验不好,为了解决这一问题,则用到了计算机系统的一种运行机制,即事件循环(Event Loop)。
同步任务和异步任务。
在javascript中,所有的任务都能分为同步任务和异步任务。
- 同步任务:立即执行的任务,直接进入主线程执行。
- 异步任务:异步执行的任务,比如ajax请求,setTimeout定时函数等。
异步任务有分为宏观任务和微观任务
- 宏观任务常见的为:setTimeout、setInterval
- 微观任务常见的有:promise.then()、MutaionObserver、process.nextTick(Node.js)
在一个事件循环中,异步事件返回的结果会被放到一个任务队列中,根据这个异步事件的类型会被对应的宏任务队列或者微任务队列。当执行栈为空的时候,主线程会查看微任务队列是否有事件存在,如果不存在,则去宏任务队列中的事件并把对应的回调加入到当前的执行栈。如果存在一次执行队列事件对应的回调,直到微任务队列为空,然后执行宏任务队列中的时间,把对应的回调加入执行栈。如此反复执行则为事件循环。
接下来我们来看个例子:
console.log('1');
setTimeout(() => {
console.log('2');
},0);
let p = new Promise((resolve, reject) => {
console.log('3');
resolve();
});
p.then(() => {
console.log('4');
})
console.log('5');
//1 3 5 4 2
我们来分析一下上面代码的执行:
1.首先打印 1
2.执行到settimeout时,此为宏任务,放入宏任务队列。
3.执行promis打印3,resolve()为微任务,放入微任务队列
4.最后打印5,到此为止这条宏任务执行完毕,接着看看是否有微任务,则promis中有一个微任务及p.then()打印4,微任务队列执行完毕。
5.查看是否有宏任务,即setTimeout,执行打印2,则结果为“1 3 5 4 2”
async await
async为异步的意思,await为等待,及await下面的代码要等待await执行完毕在执行。
async function fun() {
console.log('1');
await fun1();
console.log('2'); //阻塞
}
function fun1() {
console.log('3');
}
fun()
console.log('4');
//1 3 4 2
上面的例子中,await会阻塞下面的代码(即加入微任务队列),先执行async外面的同步代码,执行完后在回到async函数中,在执行阻塞代码,其结果为1 3 4 2。
通过对上面的了解,我们大致的了解了事件循环的执行顺序,下面我们来看一个综合的例子
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')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
分析执行过程
1.执行上面的代码首先执行console.log('script start'),打印‘script start’。
2.接着执行settimeout,其为宏任务,放入宏任务队列。
3.执行async1(),首先打印‘async1 start’,接着执行await async2(),则打印‘ async2 ’,阻塞下面的代码,放入微任务队列,跳出async1()函数,接着执行。
4.执行new Promise,打印‘promise1’,then为微任务,放进微任务队列。
5.最后执行console.log('script end');打印‘script start’。此时的同步任务执行完,接着执行微任务,及async,打印‘async1 end’
6.接着继续执行微任务,即promise.then,打印‘promise2’。
7.上一个宏任务执行完,开始下一个宏任务,即settimeout,打印‘settimeout’
上面例子的打印结果为:script start、async1 start、async2、promise1、script end、async1 end、promise2、settimeout。
事件循环图