1. 引言
在JavaScript中,事件循环是一个非常重要的概念。它决定了代码执行的顺序,并且能够控制异步操作的结果。但是,在理解事件循环之前需要先了解同步任务和异步任务。
2. 同步任务与异步任务
2.1 同步任务
同步任务是指在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。同步任务的执行顺序完全受事件循环控制。
console.log('同步任务1');
console.log('同步任务2');
console.log('同步任务3');
以上代码中,三个console.log方法都是同步任务,在主线程上依次排队执行,按照顺序输出:
// 输出
同步任务1
同步任务2
同步任务3
2.2 异步任务
异步任务是指不会阻塞主线程,而是通过事件循环机制在另一个时间点执行的任务。异步任务的执行不会影响主线程的任务队列,主线程会继续执行后续任务,等待异步任务完成后,再去执行它的回调函数。
常见的异步任务有:定时器、回调函数、Promise、async/await等。
console.log('异步任务1');
setTimeout(function(){
console.log('异步任务2');
}, 1000);
console.log('异步任务3');
以上代码中,setTimeout方法是异步任务,它会在1000ms后,把回调函数放到事件队列中等待执行,不会影响后续主线程中的执行。
// 输出
异步任务1
异步任务3
异步任务2
以上输出结果的原因是因为:在主线程中依次执行了同步任务1和3后,遇到setTimeout方法后,会把回调函数放到事件队列中等待执行,主线程会继续执行异步任务之后的同步任务3的操作。
3. 事件循环机制
3.1 定义
事件循环指的是JavaScript的一种执行机制,用来控制同步任务及异步任务的执行顺序。在JavaScript中,事件循环是由主线程和任务队列(event queue)组成。
当主线程遇到同步任务时就直接执行,如果遇到异步任务,则会将其放到任务队列中等待执行。当主线程上所有同步任务执行完毕时,它就会不断地从任务队列中取出任务,并执行对应的回调函数,直到队列为空为止。
3.2 任务队列
任务队列是用来存放异步任务的地方,队列中的任务遵循先进先出的原则。任务队列又分为宏任务和微任务。
3.3 宏任务与微任务
宏任务(macrotask)与微任务(microtask)的执行顺序不一样。在每次事件循环中,JavaScript引擎都会先执行所有的微任务,然后再去执行宏任务。
3.3.1 宏任务
宏任务可以理解为整体的代码块,包括但不限于:script、setTimeout、setInterval、I/O、UI交互事件等。
console.log('宏任务1');
setTimeout(function(){
console.log('宏任务2');
}, 0);
console.log('宏任务3');
以上代码中,console.log方法、setTimeout方法都是宏任务,在事件循环中,会优先执行整体代码块中的所有宏任务。
// 输出
宏任务1
宏任务3
宏任务2
以上输出结果的原因是因为:在事件循环中,首先执行整体代码块中的宏任务1和3,再去执行setTimeout方法,它的回调函数等待1000ms后才会被放到任务队列中去,此时主线程继续执行宏任务3,最后执行掉所有的宏任务后,再去执行任务队列中的微任务。
3.3.2 微任务
微任务一般包括promise.resolve(), mutationObserver等等,其执行的时机是在宏任务执行完后,在下一个宏任务之前执行。
console.log('微任务1');
Promise.resolve().then(function(){
console.log('微任务2');
});
console.log('微任务3');
以上代码中,console.log方法和promise.resolve方法都是微任务。
// 输出
微任务1
微任务3
微任务2
以上输出结果的原因是:在事件循环中,先执行所有的宏任务,然后执行所有的微任务。
总结
在JavaScript事件循环中,同步任务和异步任务的执行顺序是由事件循环机制决定的。而任务队列则是用来存放异步任务的地方,任务队列又分为宏任务和微任务两种。程序执行到事件循环时,JavaScript引擎会优先执行整体代码块中的所有宏任务,然后再去执行所有微任务的回调函数,最后再去任务队列中取出宏任务的回调函数执行,整个过程是一个循环过程。