1. 什么是Event Loop
js是单线程的。即指程序运行时,只有一个线程存在,同一时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
为什么要这么设计,跟 JavaScript 的应用场景有关。
JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?
- 所有同步任务都在主线程上执行,形成一个执行栈
- 主线程之外,还存在一个"任务队列"(task queue)” 。异步任务进入任务队列
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 4.主线程不断重复上面的第三步。
只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会不断重复。这种机制就叫做事件循环(Event Loop)
2. 任务队列
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。比如:ajax网络请求,setTimeout定时函数等都是异步任务。
3.宏任务和微任务
微任务
一个需要异步执行的函数,执行时机是在主函数执行结束之后、当前宏任务结束之前
常见的微任务有:
- Promise.then()
- process.nextTick(Node.js)
宏任务
宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合
常见的宏任务有:
- setTimeout/setInterval
- setImmediate、I/O事件(Node.js)
这时候,事件循环,宏任务,微任务的关系如图
按照这个流程,它的执行机制是:
- 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
- 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完
看下面的题目
console.log(1) // 第一步先执行 打印 1
setTimeout(()=>{ // 第二步 定时器属于宏任务 先不执行 放着等当前宏任务下面执行
console.log(2) // 开启下一个宏任务 打印 2
}, 0)
new Promise((resolve, reject)=>{
console.log('new Promise') // 第三步打印 'new Promise'
resolve()
}).then(()=>{
console.log('then') // 第四步 .then 是微任务 先不执行放入微任务队列 当前宏任务执行完再去执行执行
// 第六步 回来执行微任务 打印 then
})
console.log(3) // 第五步 执行 打印 3当前宏任务执行完了,再去找微任务队列里的任务 ,先进先出。
4. async与await
async 是异步的意思,await 则可以理解为等待
放到一起可以理解async就是用来声明一个异步方法,而 await 是用来等待异步方法执行
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
console.log(3)
上面的例子中,await 会阻塞下面的代码(即加入微任务队列),先执行 async 外面的同步代码,同步代码执行完,再回到 async 函数中,再执行之前阻塞的代码.
所以输出顺序为 :1 => fn2 => 3 => 2
基于上面的了解,我们试试下面的题
// 程序自上而下执行
async function async1() {
console.log('async1 start') // 第四步 打印 async1 start
await async2() // 第五步 执行async2
console.log('async1 end')
}
async function async2() {
console.log('async2') // 第六步 打印 async2
}
console.log('script start') // 第一步 打印 scriipt start
setTimeout(function () { // 第二步放到 下一个宏任务
console.log('settimeout') // 第十一步 打印settimeout
})
async1() // 第三步 执行async1
new Promise(function (resolve) {
console.log('promise1') // 第七步 打印promise1
resolve()
}).then(function () {
console.log('promise2') // 第八步 放到微任务队列等待执行
// 第十步 打印 promise2 微任务全部做完了
// 进行下一个宏任务也就是setTimeout
})
console.log('script end') // 第九步 打印 script end 当前主线程执行完成 去查找当前的微任务
所以整个打印过程是: cript start、 async1 start、async2、promise1、script end、async1 end、promise2、settimeout