JavaScript事件循环深度剖析:从单线程到异步并发的核心机制

127 阅读3分钟

EVENT-LOOP

前言(引言):

JavaScript 作为一门单线程语言,却能高效处理高并发任务,其秘密就在于​​事件循环(Event Loop)​​机制。你是否曾困惑:

  • 为什么 setTimeout(fn, 0) 不立即执行?
  • Promise 和 setTimeout 的回调谁先触发?
  • 事件循环如何像交通指挥员​​一样调度异步任务
  • ​宏任务与微任务的优先级博弈​​(为什么 Promise 总是先于 setTimeout)
  • 无论你是想通过面试,还是优化高性能应用,理解事件循环都是进阶 JavaScript 开发的必经之路。现在,让我们从最简单的任务队列说起……

进程线程

1. 进程:

CPU 在运行指令和保存上下文所需要的时间

eg: 手机打开微信,微信系统在执行打开指令,到加载微信的上下文环境(源代码),直到彻底关闭微信之前的这段时间,都是在一个进程中

2. 线程:

是一个进程中的一个更小的单位,指的是执行一段指令所需的时间。

eg: 打开微信聊天界面,就需要一个渲染线程,同时获取到最新的消息,需要一个网络线程

js 是单线程的: 是js在执行一份代码时只会进行单线程

面试题:

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

  1. http 线程

  2. js 引擎线程

  3. 渲染线程

异步

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

EVENT-LOOP

微任务
  1. promise.then

  2. process.nextTick(node 里面独有的一个方法,浏览器里面没有)

  3. MutationObserver

宏任务
  1. setTimeout

  2. setInterval

  3. ajax

  4. setImmediate(已被弃用)

  5. I/O (输入/输出)

  6. UI rendering (页面渲染)

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

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

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

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

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

async function foo() { await a() b() } foo() 类似于.then()函数

  1. 会将后续的代码挤入微任务队列

  2. 浏览器将 await 的执行时间提前了(await 后面的代码要当初同步来看待)

实例训练

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);

答案:1 2 7 3 5 4 6

图解:

QQ_1751093777877.png

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');   

答案:script start ---> async2 end ---> promise ---> script end ---> async1 end ---> then1 ---> then2 ---> setTimeout

图解:

QQ_1751095702667.png

看懂图解才是关键