JavaScript 事件循环四部曲

87 阅读4分钟

进程线程

  1. 进程:CPU在指令和保存上下文对象需要的时间 (比如:手机打开微信,系统在执行打开指令到加载微信上下文环境,直到彻底关闭微信之前的这段时间,都是一个进程)
  2. 线程:是进程中一个更小的一个单位,指的是执行一段指令所需的时间 (比如打开微信聊天界面,就需要一个渲染线程,同时获取最新消息,需要一个网路线程)

浏览器新开一个tab页面就是一个新的进程,这个进程中有很多线程相互配合工作,最后展示页面给到用户 这个过程涉及到的线程有:

  1. http 线程
  2. js引擎线程
  3. 渲染线程

线程之间通常都可以同时工作,但是只有js引擎线程和渲染线程是互斥的,但是其他线程之间是可以同时工作

异步

  • v8在执行js代码时默认只开启一个线程工作,(js是单线程的 是指v8在执行一份js代码时,只会开一个线程执行),所以考虑到执行效率,v8会先执行同步代码,遇到异步代码,会将异步代码存放到任务队列(微任务队列、宏任务队列),等到js引擎空闲时,再从任务队列中取出异步代码,执行异步代码

image.png

image.png 跳过异步先执行同步

EventLoop

  • js代码中有同步和异步之分,异步还被分为宏任务和微任务

微任务:promise.then , process.nextTick, MutationObserver(.then是微任务)

宏任务:setTimeout, setInterval, ajax, setImmediate(已废弃), I/O,

UI rendering(页面渲染)

  • 执行顺序:

1.先执行同步代码(这属于宏任务),这个过程中遇到异步,就存入任务队列(微任务队列、宏任务队列)

2.同步执行完毕后,先执行微任务队列中的代码

3.微任务全部执行完毕后,有需要的情况下渲染页面

4.渲染完毕后,执行宏任务队列中的代码(开启下一次事件循环)

console.log(1);
new Promise((resolve) => {
  console.log(2);
  resolve();
})
.then(() => {
  console.log(3);
  setTimeout(() => {
    console.log(4);
  }, 0);
});

setTimeout(() => {
  console.log(5);
  setTimeout(() => {
    console.log(6);
  }, 0);
}, 0);

console.log(7);

image.png

具体实现过程:

QQ20250627-191208.png

先执行同步代码,1 ,2执行完,遇到异步代码.then将异步代码放入微任务队列,继续执行遇到setTimeout放入宏队列代码,最后继续执行同步代码3,执行完同步代码,此时微任务的队列中微任务出队列,执行微任务,执行4后,继续执行遇到宏任务,加入宏任务队列,此时微任务执行完毕,查看是否需要渲染,不需要,执行宏任务,执行5,6,7,最后打印结果为1273546

async和await

image.png
function a() {
  setTimeout(() => {
    console.log("a");
  }, 1000);
}
function b() {
  console.log("b");
}
function foo() {
  a();
  b();
}
foo();

%E5%B1%8F%E5%B9%95%E5%BD%95%E5%88%B6%202025-06-28%20152645_converted.gif 如果要执行以上代码,让a先执行完,可以用回调的形式

也可以使用.then的形式

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("a");
     resolve();
    }, 1000);
  });
}
function b() {
  console.log("b");
}
function foo() {
a().then(() => {
  b();
});

}
foo();

但是这样依然看着很繁琐,于是便有了 async 和 await的出现

es7新加 async是语法糖,是return new Promise的封装,await相当于.then()

image.png

async写法: image.png

注意 语法规定await必须被包裹在async里面,所以我们每次使用时都必须多加一个外层async的外层函数, 于是上面的代码就可以写成

function a() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("a");
      resolve();
    }, 1000);
  });
}

function b() {
  console.log("b");
}
async function foo() {
  await a().then(() => {
    b();
  });
}

foo();

await

  1. 会将后续的代码挤入微任务队列(后续指的是下一行及以下的代码)

  2. 浏览器对 await的执行时间提前了(await 后面的代码当作同步来看待,是await 紧挨着的代码)

console.log('script start');
async function async1() {
  await async2() // 
  console.log('async1 end'); 
}
async function async2() {
  console.log('async2 end');
}
async1()
setTimeout(() => {
  console.log('setTimeout');
}, 0)
new Promise((resolve, reject) => {
  console.log('promise');
  resolve()
})
.then(() => {
  console.log('then1');
})
.then(() => {
  console.log('then2');
});
console.log('script end');

image.png

QQ20250627-194725.png

按事件循环四部曲: 先执行同步代码1,然后执行第9行时,遇到await修饰的async2(),会先执行async2() 就是执行2,await修饰代码的后续代码进入微任务队列,执行同步3,触发。then()执行 .then(log(then1)).then(log(then2))进入微任务队列,执行同步4,同步执行完毕,执行微任务队列,5,6,微任务执行完毕,查看是否需要渲染,不需要,执行宏任务队列,7,要想await修饰的先执行,必须确保修饰的函数调用的函数体是async修饰或则返回了new promise。

如有错误欢迎指正,欢迎友友们点赞评论