Event Loop 学习笔记

387 阅读4分钟

1. 什么是Event Loop

js是单线程的。即指程序运行时,只有一个线程存在,同一时间只能做一件事。单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。

为什么要这么设计,跟 JavaScript 的应用场景有关。

JavaScript 初期作为一门浏览器脚本语言,通常用于操作 DOM ,如果是多线程,一个线程进行了删除 DOM ,另一个添加 DOM,此时浏览器该如何处理?

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,还存在一个"任务队列"(task queue)” 。异步任务进入任务队列
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。 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)

这时候,事件循环,宏任务,微任务的关系如图

微信图片_20210318130554.jpg

按照这个流程,它的执行机制是:

  • 执行一个宏任务,如果遇到微任务就将它放到微任务的事件队列中
  • 当前宏任务执行完成后,会查看微任务的事件队列,然后将里面的所有微任务依次执行完

看下面的题目

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 startasync1 startasync2promise1script endasync1 endpromise2settimeout