一次学会Event Loop

273 阅读4分钟

最近我们前端团队在做技术分享,自己一直想讲讲Event Loop,于是研究了两天,发现自己之前有很多知识点都遗漏了,所以记录下来,与大家分享。

在讲EventLoop之前,首先得搞明白几个概念。

1 同步和异步

同步和异步的概念相信各位大佬已经很熟悉了,但为了刚入行的同学着想,还是说明一下:

1.1 同步

如果在一个函数返回的时候,调用者就能够得到预期结果,那么这个函数就是同步的。 也就是说同步方法调用一旦开始,调用者必须等到该函数调用返回后,才能继续后续的行为。

1.2 异步

比如发一个请求,我们告诉主程序等到接收到数据后再通知我,然后我们就可以去做其他的事情了。当异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,再去执行。

大白话总结:同步需要等待,一个执行完再执行另一个;异步不需要等,可以同时进行。

2 宏任务和微任务

2.1 宏任务

宏任务指执行栈中待执行的任务,计时器,事件回调,http回调都是宏任务,比如以下几种,其中咱们最常用的就是setTimeout和setInterval。

2.2 微任务

微任务指执行栈清空后立即执行的任务(可以理解为VIP通道~),Promise 和 MutationObserver都是微任务,其中最常用的就是Promise。

2.3 优先级细节

宏任务macrotask: 主代码块 > setImmediate > MessageChannel > setTimeout / setInterval

微任务microtask: process.nextTick > Promise = MutationObserver

就是说任务执行的顺序是建立与优先级之上的: 如果队列已经有一个setTImeout的宏任务,后来又加入了主代码的宏任务,会让主代码的的任务插队。

牢记一点:微任务优先执行,接下来是宏任务


3 promise

promise本身是同步的,promise的then方法和catch方法才是异步的,我们举个栗子:

console.log(1)
let a = new Promise((res,rej) => {
   console.log(2);
});
console.log(3);
let b = new Promise((res,rej) => {
    console.log(4);
});
console.log(5);

输出是什么呢?1,3,5,2,4吗?

答案是:1,2,3,4,5

4 Event Loop

好了,终于进入了主题!先说说Event Loop是什么意思。

Event Loop就是JavaScript的事件循环,它指的是计算机系统的一种运行机制。JavaScript语言就采用这种机制,来解决单线程运行带来的一些问题。

  • 接下来我们来做几道题。
setTimeout(() => {
  console.log('1');
}, 0);
var obj = {
  funcfunction() {
    setTimeout(function() {
      console.log('2');
    }, 0);
    return new Promise(function(resolve) {
      console.log('3');
      resolve();
    });
  },
};
obj.func().then(function() {
  console.log('4');
});
console.log('5');

【解析】

第一个 setTimeout 放到宏任务队列,此时宏任务队列为 ['1']

接着执行 obj 的 func 方法,将 setTimeout 放到宏任务队列,此时宏任务队列为 ['1', '1']

函数返回一个 Promise,因为这是一个同步操作,所以先打印出 '3'

接着将 then 放到微任务队列,此时微任务队列为 ['4']

接着执行同步任务 console.log('5');,打印出 '5'

因为微任务优先执行,所以先输出 '4'

最后依次输出 '1' 和 '2'

  • 最后再看一道经典考题,做好准备:
function async1() {
  console.log('1');

  Promise.resolve(async2()).then(() => {
    console.log('2');
  });
}

function async2() {
  console.log('3');
}

console.log('4');

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

async1();

new Promise(function(resolve) {
  console.log('6');
  resolve();
}).then(function() {
  console.log('7');
});
console.log('8');

【解析】

首先打印出 '4'

接着将 settimeout 添加到宏任务队列,此时宏任务队列为 ['5']

然后执行函数 async1,先打印出 '1',又因为 Promise.resolve(async2()) 是同步任务,所以打印出 '3',接着将 '2' 添加到微任务队列,,此时微任务队列为 ['2']

接着打印出 '6',将 '7' 添加到微任务队列,,此时微任务队列为 ['2', '7']

打印出 '8'

因为微任务优先级高于宏任务,所以先依次打印出 '2' 和 '7'

最后打印出宏任务 '5'

怎么样,现在再做是不是感觉到比较轻松了呢。

关于作者:JeremyCC

一个爱唱歌的前端工程狮,喜欢我可以点关注噢!(头像仅供参考,哈哈)