做题学知识(3)之 Event Loop

问题

第一题

问多久会弹出来 ok?

let val = truesetTimeout(function () {    val = false}, 3000)while(val){}alert('OK')

第二题

问多久会打印出来 1

setTimeout(function () {    console.log(1)}, 3000)
for(....) // 一个执行 5000ms 的for 循环

第三题

问打印顺序是什么样的?

setTimeout(function () {    console.log(1)}, 3000)
setTimeout(function () { console.log(2)}, 1000)
for(....) // 一个执行 5000ms 的 for 循环

答案

因为 JS 是单线程的要想实现异步操作需要采用一种机制这种机制就是 Event Loop,而上面的三道题主要考察了这个知识点。先来看一张图

可以看到图中主要分为了四部分:Heap(堆),Stack(栈),Queue(队列),WebAPIs。其中的 Heap(堆)可以不用考虑,因为不影响本文的阅读。(这里不讨论宏任务和微任务,微任务会在扩展阅读中加入)

当 JS 代码开始执行的时候就是开始一个循环:Stack(栈)中为空,Queue(队列)中有一个 main 主函数,将 main 函数放入 Stack(栈)中,在主函数中会碰到一些 WebAPIs,如果是异步的通常都会有个 callback function(回调函数)当异步完成之后 WebAPIs 会将这个callback function(回调函数)放入 Queue(队列)中,当 Stack(栈)中的内容全部出栈的时候就开始下一个循环将 Queue(队列)第一个任务放入栈中以此循环执行下去。

第一题

问多久会弹出来 ok?

let val = truesetTimeout(function () {    val = false}, 3000)while(val){}alert('OK')

分析一下代码的执行情况:

  1. 开始状态:Stack[],Queue[Main]

  2. 第一次循环将 Queue 的第一个任务放到 Stack 中: Stack[Main],Queue[]

  3. 由于 Main 中有 setTimeout 所以放到 WebAPIs 中以待合适的时机将 callback function 放入 Queue 中

Main 中的代码简化之后如下:

let val = truewhile(val){}alert('OK')

可以看到里面有个 while 的死循环,因此 Stack 中的代码永远执行不完。当 3000ms 之后。WebAPIs 处理完成会将 setTimeout 入队 Queue[setTimeout],但是由于 Stack 中的代码永远执行不完因此下一个 Event 循环不会开启。答案是永远不会弹出

第二题

问多久会打印出来 1

setTimeout(function () {    console.log(1)}, 3000)
for(....) // 一个执行 5000ms 的for 循环

分析一下代码的执行情况:

  1. 开始状态:Stack[],Queue[Main]

  2. 第一次循环将 Queue 的第一个任务放到 Stack 中: Stack[Main],Queue[]

  3. 由于 Main 中有 setTimeout 所以放到 WebAPIs 中以待合适的时机将 callback function 放入 Queue 中

  4. 代码执行 3000ms 的时候 setTimeout 执行完成入队:Stack[Main], Queue[setTimeout]

  5. 代码执行 5000ms 的时候栈中代码全部完成: Stack[],Queue[SetTimeout]

  6. 开始下一个循环, Queue 队列中第一个任务出队入栈:Stack[setTimeout],Queue[]

  7. 栈中代码执行,1 会打印出来,**因此答案是 5000ms **

第三题

问打印顺序是什么样的?

// 我在 Queue 中表示为 set1setTimeout(function () {    console.log(1)}, 3000)
// 我在 Queue 中表示为 set2setTimeout(function () { console.log(2)}, 1000)
for(....) // 一个执行 5000ms 的 for 循环

分析一下代码的执行情况:

  1. 开始状态:Stack[],Queue[Main]

  2. 第一次循环将 Queue 的第一个任务放到 Stack 中: Stack[Main],Queue[]

  3. 由于 Main 中有 setTimeout 所以放到 WebAPIs 中以待合适的时机将 callback function 放入 Queue 中

  4. 代码执行 1000ms 的时候 set2 的 WebAPIs 执行完成:Stack[Main], Queue[set2]

  5. 代码执行 3000ms 的时候 set1 的 WebAPIs 执行完成: Stack[Main],Queue[set2, set1]

  6. 代码执行到 5000ms 的时候 Stack 中代码执行完成,Queue 中第一个任务出队进入 Stack 中:Stack[set2], Queue[set1]

  7. 如此执行因此答案是:2,1

扩展阅读

这里借用一下 《JavaScript 忍者秘籍》的图,如果所示这就是一个完整的 Event Loop 牵扯到的东西,可以简单理解为将之前 Queue 分为了三个部分 Task(宏任务)队列、microTask(微任务)队列、UI渲染。循环步骤如下:

  1. 将 Task 队列中的第一个 Task 压入栈执行,第一个宏任务是 Main 函数

  2. Main 函数执行完成,依次执行 microTask 中所有的任务

  3. microTask 中所有的任务执行完成进行一次 UI 渲染

  4. 再接着将 Task 队列中的第一个 Task 压入栈,如此反复执行

你可能要问了,为什么要有 Task 和 microTask 之分?这里你要考虑俩个常见的场景:

  1. Task 中的任务是一直往后排的,当你某个任务想要快速响应的时候你是做不到的,因此你用一个 microTask 就能够插队优先执行

  2. UI 渲染是最后执行的,你有时候想要在本次渲染完成之前进行一次数据变更。这时候也需要用到 microTask

为了加深理解这里也来几道题:

setTimeout(() => {    console.log(1)}, 0)<!--这个在 microTask 中叫 P1-->Promise.resolve().then(() => {    console.log(2)    <!--这个在 microTask 中叫 P2-->    Promise.resolve().then(() => {        console.log(3)    })})

setTimeout 和 Promise 都是异步队列,请问输出情况是什么样子的?

答案是:2,3,1

答案解析:

  1. 开始状态: Stack[],Task[Main],microTask[]

  2. Task 队列第一个 Task 压入栈: Stack[Main],Task[],microTask[]

  3. 栈执行完毕: Stack[], Task[setTimeout], microTask[P1]

  4. 开始依次执行 microTask 队列中的所有任务:P1 执行,打印 2,发现新的 microTask 加入 microTask 队列: Stack[], Task[setTimeout], microTask[P2]

  5. 第一个 microTask 执行完毕,检查 microTask 队列是否还有,发现还有 P2,执行,打印 3:Stack[], Task[setTimeout], microTask[]

  6. microTask 队列中没有任务,检测是否需要 UI 渲染,一轮循环完成,将 Task 队列中的第一个 Task 压入栈:Stack[setTimeout], Task[], microTask[]

  7. 栈中代码执行完成,打印 1:Stack[], Task[], microTask[]

  8. 等待新的 Task 进入 Task 队列

总结

整个 Event Loop 过程是有迹可循的,关键是明白这个过程。并且区分出来哪些异步任务是 Task 和 microTask

我创建了一个交流群,欢迎大家扫码关注公众号进行获取。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值