Eventloop?记住规则,没那么难。

前言

我们都知道 JavaScript 是一门 单线程 的语言:同一时间只能运行一个任务。通常情况下这没什么问题,但是如果你有一个任务需要耗费 30 秒的时间,那其他任务难道都要等它 30 秒么?(由于 JS 运行在浏览器的主线程,所以这 30 秒的时间里,整个页面都会处于卡死状态)

幸运的是,浏览器提供了一些 JS 引擎不具备的功能:Web API。它包括 DOM API,setTimeout,HTTP 请求 等等。这些功能都可以帮助我们处理 异步、非阻塞 的操作。

什么是Event loop?

Event Loop 即事件循环,是指浏览器或Node的一种解决Javascript单线程运行时不会阻塞的一种机制,也就是异步的原理。

为啥要弄懂Event loop?

  • 增加技术深度,也就是懂得Javascript的运行机制。
  • 前端领域技术层出不穷,掌握底层原理可以以不变应万变。
  • 应对大场面试,懂其原理,题目任意发挥。

数据结构前置知识

堆:利用完全二叉树维护的一组数据,是线性数据结构,相当于一维数组,有唯一后继。

栈:后进先出。类似桶装薯片,包装的时候只能从顶部放入,而吃的时候也只能从顶部拿出。

队列:先进先出。类似排队办理业务,排在最前面的最先办理。

调用栈:本质上当然还是个,它里面装的东西是一个个待执行的函数,记住,是函数!先拿两个函数来说:

  • 栈空
  • 现在执行到一个 函数A,函数A 入栈
  • 函数A 又调用了 函数B,函数B 入栈
  • 函数B 执行完后 出栈
  • 然后继续执行 函数A,执行完后A也 出栈
  • 栈空

调用栈.gif

Event loop

同步任务、异步任务

Js单线程分为同步任务和异步任务。

同步任务会在调用栈中按照顺序等待主线程依次执行。

异步任务会在异步任务有了结果后,将注册的回调函数放入任务队列中,等调用栈为空时,会被读取到调用栈中等待主线程执行。

回调函数

很多人看到“回调函数”就准备发懵了,但其实很简单。打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒的方式”是由旅客决定并告诉旅馆的,旅馆会在第二天早上执行。这个“叫醒的方式”就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为注册回调函数(to register a callback function)。

宏任务、微任务

在Js中,任务被分为两种,宏任务(Macro Task,Task)和微任务(Micro Task)。

宏任务包括:

  • script全部代码
  • setTimeout
  • setInterval
  • setImmediate
  • I/O
  • UI Rendering

微任务包括:

  • Process.nextTick(Node 独有)
  • Promise.then之后的任务
  • Mutation Observer

可以看到,宏任务中既有同步任务,也有异步任务。

Event Table

Event Table 可以理解成一张 事件->回调函数 对应表

它就是用来存储 JavaScript 中的异步事件 (request, setTimeout, IO等) 及其对应的回调函数的列表

Event Queue

Event Queue 简单理解就是 回调函数 队列,所以它也叫 Callback Queue

当 Event Table 中的事件被触发,事件对应的 回调函数 就会被 push 进这个 Event Queue,然后等待被执行

Event Loop

一个流程图秒懂:

image.png

Event Loop规则,记住规则,万事easy~

  1. js执行时,用到了两个数据结构,执行栈(call Stack)和任务队列(Event Queue)(都是软件范畴哦,其实都是数组,只不过按照特性抽象成了栈和队列)。

  2. 执行之前,首先会按照代码顺序将同步任务会被依次加入执行栈中依次执行,将异步任务分为宏任务和微任务分别放入宏任务队列和微任务队列。

  3. 在执行栈执行完同步任务(算作第一个宏任务)后,查看执行栈是否为空,如果执行栈为空,检查微任务队列是否为空,如果不为空,会按照先进先出的规则全部执行完微任务后,就会去执行第二个宏任务,每一个宏任务执行完毕后,检查微任务队列是否为空,如果不为空,会按照先进先出的规则全部执行完微任务后,再执行下一个宏任务,如此循环。

  4. Promise在.then之前的代码是同步任务,直接执行,.then之后的才是异步任务,也是微任务,需要被放入微任务队列。

例子-经典面试题,举一反三

setTimeout(function () {
    console.log(1)
}, 0);

new Promise(function (resolve, reject) {
    console.log(2);
    resolve();
}).then(function () {
    console.log(3)
}).then(function () {
    console.log(4)
});

console.log(6);
复制代码
  1. 执行主线程同步任务:Promise.then前面的console.log(2);和 console.log(6),输出 2 6
  2. setTimeout丢入宏任务队列,Promise.then丢入微任务队列。
  3. 因为第1步执行了第一个宏任务,现在开始执行微任务,输出 3 4
  4. 执行下一个宏任务:输出 1

最终答案;2 6 3 4 1

setTimeout(function () {
    console.log(1)
}, 0);

new Promise(function (resolve, reject) {
    console.log(2)
    for (var i = 0; i < 10000; i++) {
        if (i === 10) {
            console.log(10)
        }
        i == 9999 && resolve();
    }
    console.log(3)
}).then(function () {
    console.log(4)
})
console.log(5);
// 2 10 3 5 4 1
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享