什么是事件循环
首先, JavaScript
是一门单线程的语言,意味着同一时间内只能做一件事,这并不意味着单线程就是阻塞,而是实现单线程非阻塞的方法就是事件循环
在JavaScript中,所欲任务都可以分为:
- 同步任务:立即执行的任务,同步任务一直会直接进入到主线程中执行
- 异步任务:异步执行的任务,比如
ajax
网络请求,setTimeout
定时任务等等
从上面可以看到,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空,会去任务队列中读取相应的任务,推入主线程执行。上面的过程不断重复就叫事件循环
宏任务和微任务
异步任务还可以细分为微任务和宏任务
微任务
一个需要异步执行的函数,执行时机主函数执行结束之后,当前宏任务执行之前
常见的微任务有:
- Promise.then
- MutationObserver(监听指定DOM的变化)
- Process.nextTick(Node.js)
常见的宏任务有:
- setTimeout/setInterval
- postMessage、MessageChannel
- UI rendering/UI事件(下轮事件循环执行之前)
- Script(外层的同步代码)
- setImmediate、I/O(Node.js)
宏任务更像在系统层面上执行的任务,微任务更像在代码层面执行的任务
按照这个顺序,它的执行机制是:
- 执行一个宏任务,如果遇到一个微任务就将它放到微任务的事件队列中
- 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完成
看一下的一个示例:
console.log(1)setTimeout(()=>{console.log(2)}, 0)new Promise((resolve, reject)=>{console.log('new Promise')resolve()}).then(()=>{console.log('then')})console.log(3)
/** * 遇到console.log(1) 直接打印 * setTimeout 是宏任务 放到宏任务队列里面 * Promise 中的代码是直接打印的 所以执行console.log('new Promise') * then 是微任务 放到微任务队列中 * console.log(3) 这直接打印 * 开始执行异步任务 * 首先执行微任务 then 中的代码 打印then * 执行完微任务执行宏任务setTimeout中的代码 打印2**/
打印的结果是:1 new Promise 3 then 2
async 与 await
async
是异步的意思,await
可以理解为async wait
,可以理解async就是用来声明一个异步方法,而await
是用来等待异步方法执行
async
async 修饰的函数返回的是一个 Promise
对象,下面的两种方法是等效的
function f() {return Promise.resolve('TEST');}// asyncF is equivalent to f!async function asyncF() {return 'TEST';}
await
正常情况下,await
命令后面是一个 Promise
对象,返回该对象结果,如果不是Promise
对象,就直接返回对应的值
async function f(){// 等同于// return 123return await 123}f().then(v => console.log(v)) // 123
不管await后面跟着的是什么,await都会阻塞后面的代码
async function fn1 (){console.log(1)await fn2()console.log(2) // 阻塞}async function fn2 (){console.log('fn2')}console.log(3)fn1()
上面的例子中,await
会阻塞下面的代码运行,先执行async外面的同步代码,同步代码执行完成后,再回到async函数中执行await之后的代码,也就是阻塞的代码
所以上述输出结果为:3 1 fn2 2
流程分析
通过对上面的了解,我们对JavaScript
的各个场景的执行顺序有了大致的了解
请看以下的代码:
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')
结果是:script start -> async1 start -> async2 -> promise1 -> script end -> async1 end -> promise2 -> settimeout
分析过程
/** * 1.遇到async1,async2函数定义不用执行, * 2.执行 console.log('script start') -> 输出:script start * 3.setTimeout 是宏任务 放到宏任务里面 * 4.执行async1() * 5.进入执行async1 里执行 console.log('async1 start')-> 输出:async1 start * 6.遇到await 执行 async2,然后阻塞await后面的代码 * 7.进入执行async2 里执行 console.log('async2')-> 输出:async2 * 8.遇到Promise 执行 console.log('promise1') -> 输出:promise1 * 9.执行console.log('script end') -> 输出:script end * 10.开始执行异步任务 * 11.执行微任务await后面的代码 console.log('async1 end')-> 输出:async1 end * 12.执行微任务then里面的代码 console.log('promise2')-> 输出:promise2 * 13.执行宏任务setTimeout里面的代码 console.log('settimeout')-> 输出:settimeout**/