前端学习17 事件循环(Event Loop)机制

JavaScript作为一门单线程语言,却能够高效处理异步任务,这一特性使其在处理网络请求、用户交互等场景下表现出色。而这一切的背后,都离不开Event Loop(事件循环)这一核心机制。

1.JavaScript的单线程

JavaScript是一门单线程语言,这意味着它只有一个主线程用于执行代码。这一设计决策主要是为了简化DOM操作的复杂性,避免多线程可能导致的并发问题。然而,单线程也带来了一个明显的问题:如果某个任务执行时间过长,会导致整个应用"卡住",无法响应用户交互。

为了解决这个问题,JavaScript引入了异步编程模型,而Event Loop就是这个模型的核心机制。

1.1 核心组件

JavaScript运行时环境主要由以下几个部分组成:

  • 调用栈(Call Stack):用于追踪函数调用的栈结构,记录当前执行的代码位置。
  • 堆(Heap):用于存储对象、数组等复杂的数据机构的内存区域
  • 任务队列(Task Queues):存储待执行的回调函数 宏任务队列和微任务队列
  • Web API/Node API:由运行环境(浏览器/Node.js)提供的API,如定时器、网络请求等

而是事件循环(Event Loop):协调调用栈和任务队列之间的关系

1.2 调用栈

调用栈是一种LIFO(后进先出)的数据结构,用于跟踪代码的执行位置。当调用一个函数时,会将其压入栈顶;当函数执行完毕,会从栈顶弹出。

function multiply(a, b) {
  return a * b;
}

function square(n) {
  return multiply(n, n);
}

function printSquare(n) {
  const result = square(n);
  console.log(result);
}

printSquare(5);

  1. 将printSquare(5)压入栈
  2. 在printSquare内部,将square(5)压入栈
  3. 在square内部,将multiply(5, 5)压入栈
  4. multiply计算结果并返回,从栈中弹出
  5. square获得结果并返回,从栈中弹出
  6. printSquare打印结果并结束,从栈中弹出

此时,调用栈为空,标志着同步代码执行完毕。

2. 事件循环核心机制

2.1 事件循环流程

  1. 执行同步代码,这些代码会立即进入调用栈执行
  2. 调用栈清空后,检查微任务队列,依次执行所有微任务
  3. 微任务队列清空后,取出一个宏任务执行
  4. 宏任务执行完毕后,再次检查微任务队列,执行所有微任务
  5. 重复步骤3和4,形成一个循环

2.2 宏任务(Macrotask)和微任务(Microtask)

理解宏任务(Macrotask)和微任务(Microtask)的区别是掌握事件循环的关键。

宏任务(Macrotask)包括:

  • setTimeout和setInterval回调
  • setImmediate回调(Node.js环境)
  • I/O操作回调
  • UI交互事件
  • requestAnimationFrame(浏览器环境)
  • MessageChannel回调

微任务(Microtask)包括:

  • Promise的thencatchfinally回调
  • queueMicrotask回调
  • MutationObserver回调(浏览器环境)
  • process.nextTick回调(Node.js环境,优先级高于其他微任务)

微任务优先级高于宏任务,即当前宏任务执行完后,会先清空微任务队列,再执行下一个宏任务。

console.log('1. 同步代码开始');

setTimeout(() => {
  console.log('2. 宏任务(setTimeout回调)');
  
  new Promise(resolve => {
    console.log('3. 宏任务中的同步代码');
    resolve();
  }).then(() => {
    console.log('4. 宏任务中的微任务');
  });
}, 0);

new Promise(resolve => {
  console.log('5. 同步代码中的Promise');
  resolve();
}).then(() => {
  console.log('6. 微任务');
});

console.log('7. 同步代码结束');

输出顺序为:

  • 1. 同步代码开始
  • 5. 同步代码中的Promise
  • 7. 同步代码结束
  • 6. 微任务
  • 2. 宏任务(setTimeout回调)
  • 3. 宏任务中的同步代码
  • 4. 宏任务中的微任务

执行过程分析:

  • 首先执行同步代码,输出"1. 同步代码开始"
  • 遇到setTimeout,将其回调放入宏任务队列
  • 遇到Promise构造函数,其内部代码是同步执行的,输出"5. 同步代码中的Promise"
  • 将Promise的then回调放入微任务队列
  • 输出"7. 同步代码结束"
  • 同步代码执行完毕,检查微任务队列,执行Promise的then回调,输出"6. 微任务"
  • 微任务队列清空,从宏任务队列取出setTimeout回调执行
  • 在setTimeout回调中,输出"2. 宏任务(setTimeout回调)"
  • 遇到新的Promise,输出"3. 宏任务中的同步代码"
  • 将新Promise的then回调放入微任务队列
  • setTimeout回调执行完毕,检查微任务队列,执行Promise的then回调,输出"4. 宏任务中的微任务"

3.浏览器的事件循环

浏览器环境中的事件循环有其独特的特性,特别是与渲染管道的交互。

在浏览器环境中,渲染步骤(样式计算、布局、绘制等)通常发生在宏任务之间,且在所有微任务执行完毕之后。这意味着,如果你想在下一次渲染前操作DOM,应该使用微任务或requestAnimationFrame(用于在下一次浏览器重绘之前执行指定的回调函数。它的作用是告诉浏览器我们希望执行一段动画,并在动画执行时进行优化,以获得更流畅的效果)。

// 不建议的写法:可能导致多次不必要的重排
button.addEventListener('click', () => {
  box.style.width = '100px';
  box.style.height = '100px';
  box.style.margin = '20px';
});

// 优化的写法:所有DOM操作合并到下一帧执行
button.addEventListener('click', () => {
  requestAnimationFrame(() => {
    box.style.width = '100px';
    box.style.height = '100px';
    box.style.margin = '20px';
  });
});

浏览器环境中的setTimeout和setInterval并不保证在指定时间后精确执行,只能保证在指定时间后将回调胶乳宏任务队列。如果调用栈或其他宏任务占用主线程,定时器回调会被延迟执行。

此外,大多数浏览器对不活跃标签页中的定时器有最小间隔限制(通常为1000ms),以节省系统资源。

4.事件循环与异步模式

4.1 回调地狱

// 回调地狱示例
getData(function(a) {
  getMoreData(a, function(b) {
    getEvenMoreData(b, function(c) {
      getFinalData(c, function(result) {
        console.log(result);
      }, handleError);
    }, handleError);
  }, handleError);
}, handleError);

解决方案:

1、使用Promise链:将嵌套回调转换为扁平的链式调用

getData()
  .then(a => getMoreData(a))
  .then(b => getEvenMoreData(b))
  .then(c => getFinalData(c))
  .then(result => console.log(result))
  .catch(handleError);

2、使用async/await:使异步代码看起来像同步代码

async function fetchAllData() {
  try {
    const a = await getData();
    const b = await getMoreData(a);
    const c = await getEvenMoreData(b);
    const result = await getFinalData(c);
    console.log(result);
  } catch (error) {
    handleError(error);
  }
}

fetchAllData();

全部评论

相关推荐

点赞 评论 收藏
分享
评论
2
2
分享

创作者周榜

更多
牛客网
牛客企业服务