一文快速了解事件循环机制(event loop)

180 阅读4分钟

前言

JavaScript 是单线程的编程语言,只能同一时间内做一件事,按先后顺序来处理事件,但是在遇到异步事件的时候,js线程不会没有阻塞,还会继续执行,这个是因为 JavaScript 的事件循环机制起的作用。本文将对事件循环机制进行简略解析,使得读者能够快速了解事件循环机制。

事件循环机制

JavaScript 如何解决阻塞问题?

(不想了解可以滑下去直接看事件循环机制的运行机制)

  1. 单线程原理:单线程就意味着所有任务需要排队依次执行,前一个任务结束,才会执行后一个任务。
  2. 单线程产生的阻塞问题:如果前一个任务执行耗时很长,后一个任务就得一直处于等待状态。但是很多时候CPU是闲着的,后一个任务需要等待可能是因为IO设备运行的很慢或者说Ajax操作从网络读取数据很慢,不得不等着结果出来,再往下执行。
  3. 事件循环机制如何解决:
    (1) 事件循环机制将所有任务分为了两类:一类是同步任务(synchronous) :同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;一类是异步任务(asynchronous):异步任务指的是被挂起的任务,即不进入主线程、而进入任务队列(task queue) 的任务,只有任务队列通知主线程,这个异步任务可以执行了,该任务才会进入主线程被执行。
    (2) 所以由于事件循环机制,此时主线程完全可以不管IO设备或者AJAX操作,将处于等待中的任务挂起,先运行排在后面的任务。等到IO设备或者AJAX操作执行完毕或者说返回了执行结果,挂起的任务才会被放入主线程继续执行下去由此防止了阻塞。

事件循环机制的运行机制

  1. 同步任务:会直接进入主线程依次执行;
  2. 异步任务:分为宏任务(进入宏任务队列) 和 微任务(进入微任务队列);
  3. 当主线程内的同步任务执行完毕,会检查微任务的任务队列,如果有微任务,微任务就进入主线程全部执行,如果没有微任务就从宏任务队列读取下一个宏任务执行;
  4. 每执行完一个宏任务就清空一次微任务队列,此过程会不断重复,直到等待最后一个宏任务产生的同步任务和微任务执行完毕则直接结束。

image.png

同步任务、宏任务、微任务

  1. 同步任务

    • console.log()
    • async
  2. 宏任务

    • 定时器
    • ajax
    • 事件绑定
  3. 微任务

    • new Promise() 后的 then 与 catch 函数
    • await 后面同一作用域的所有任务都属于微任务

案例分析

示例一

new Promise((resolve, reject) => {
  resolve(1);
  new Promise((resolve, reject) => {
    resolve(2);
  }).then((data) => {
    console.log(data);
  });
}).then((data) => {
  console.log(data);
});

console.log(3);

任务队列:

image.png

所以执行的顺序是:

  1. 先清空同步任务:console.log(3);

  2. 然后清空微任务队列:

    • .then((data) => { console.log(data);//2 });

    • .then((data) => { console.log(data);//1 });

示例二

console.log(11);
setTimeout(() => {
  console.log(12);
  let p = new Promise((resolve) => {
    resolve(13);
  });

  p.then(res=>{
    console.log(res);
  })
  console.log(15);
}, 0);

console.log(14);

任务队列:

image.png

所以执行顺序是

  1. 先清空同步任务:console.log(3);

  2. 然后执行微任务里面的内容,但是因为没有微任务所以开始一个宏任务:然后宏任务里面有同步任务和微任务,所以任务队列变成了以下样子:

image.png

  1. 然后继续先清空同步任务:console.log(12); console.log(15);
  2. 然后清空微任务:p.then(res=>{ console.log(res); })
  3. 然后宏任务队列清空了,最后一个宏任务产生的同步任务和微任务也执行完毕,所以就结束了。

示例三

async function async1(){
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}
async function async2(){
    console.log('async2 start');
}
console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
async1();

任务队列:

image.png

所以执行的顺序是:

  1. 也是先清空同步任务:
    (1)console.log('script start');
    (2)console.log('async1 start');
  2. 然后清空微任务队列:
    (1) await async2(); => console.log('async2 start');
    (2)console.log('async1 end');
  3. 微任务清空之后,则执行一个新的宏任务:
    (1)setTimeout => console.log('setTimeout');
  4. 然后宏任务队列清空了,最后一个宏任务产生的同步任务和微任务也执行完毕,所以就结束了。

以上是我整理的一些事件循环机制知识点,如有错漏,还请指正。