likes
comments
collection
share

JS面试:谈谈JS事件循环机制以及简单的应用场景?

作者站长头像
站长
· 阅读数 26

answer

JavaScript事件循环(Event Loop)是JS处理异步编程的核心机制之一。由于JavaScript是单线程语言,它需要一种机制来处理多个任务的执行顺序,从而实现异步操作和非阻塞I/O。这种机制就是事件循环。

事件循环机制

  1. 调用栈(Call Stack)

    • JavaScript引擎运行时,会把同步任务放入调用栈中。
    • 调用栈是一个后进先出的结构,最先进入的函数最后执行,最先执行的函数最后进入调用栈。
    • 当调用栈中没有任务时,事件循环才会开始处理异步任务。
  2. 任务队列(Task Queue)

    • 异步任务(如setTimeoutsetIntervalI/O操作、事件监听等)的回调函数会放入任务队列中。
    • 当调用栈为空时,事件循环会从任务队列中取出最早进入队列的任务,将其放入调用栈中执行。
  3. 微任务队列(Microtask Queue)

    • 微任务(如Promise的回调函数、MutationObserver等)会放入微任务队列。
    • 每次事件循环时,主任务执行完后,先检查并执行所有微任务队列中的任务,然后再去任务队列中取任务。
  4. 事件循环(Event Loop)

    • 事件循环的核心就是不断检查调用栈是否为空,如果为空则检查微任务队列是否为空,执行所有微任务后,再从任务队列中取任务执行。
    • 简单来说,事件循环的顺序是:调用栈 -> 微任务队列 -> 任务队列。

事件循环的简单流程图

调用栈 (Call Stack) <--(执行主任务、同步任务)---
   |                                       |
   |                                       V
空?------------> 微任务队列 (Microtask Queue) <-- (执行所有微任务)
   |                                       |
   |                                       V
否              任务队列 (Task Queue) <--(执行一个宏任务)
   |                                       |
   V                                       V
再次检查调用栈 <--(新的主任务开始)-------是

简单的应用场景

1. setTimeoutsetInterval

console.log('Start');

setTimeout(() => {
  console.log('Timeout callback');
}, 1000);

console.log('End');

// 输出顺序:
// Start
// End
// Timeout callback (约1秒后)

2. Promiseasync/await

console.log('Start');

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

console.log('End');

// 输出顺序:
// Start
// End
// Promise callback

3. async/await 的实际应用

async function asyncTask() {
  console.log('Task start');
  
  await new Promise(resolve => setTimeout(resolve, 1000));
  
  console.log('Task end');
}

console.log('Before task');
asyncTask();
console.log('After task');

// 输出顺序:
// Before task
// Task start
// After task
// Task end (约1秒后)

总结

JavaScript的事件循环机制是为了处理异步任务,使得JavaScript能够在单线程环境下,实现非阻塞I/O和并发处理。通过理解事件循环、调用栈、任务队列和微任务队列的运作原理,开发者能够编写更高效、更有响应能力的JavaScript代码。

追问:在平常写代码中,有用到吗?

是的,JavaScript事件循环机制在日常编码中非常常用,尤其是在处理异步操作、用户交互和网络请求时。以下是一些常见的应用场景,展示了事件循环机制在实际开发中的重要性和应用。

常见应用场景

1. 处理用户交互

在Web开发中,用户交互是非常常见的场景。例如,点击按钮、输入文本等操作都会触发事件,这些事件处理程序会被放入任务队列中等待执行。

document.getElementById('button').addEventListener('click', () => {
  console.log('Button clicked');
});

在这种情况下,当用户点击按钮时,点击事件的回调函数会被添加到任务队列中,等待调用栈为空时执行。

2. 网络请求

在现代Web应用中,进行网络请求(如使用fetchXMLHttpRequest)是非常常见的操作。网络请求通常是异步的,当请求完成后会触发回调函数。

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error('Error:', error);
  });

console.log('Fetch initiated');

在这个例子中,fetch请求发出后不会阻塞主线程,thencatch回调函数会在请求完成后被放入微任务队列中执行。

3. 定时任务

使用setTimeoutsetInterval来设置延时任务或重复任务,这也是事件循环机制的常见应用。

console.log('Start');

setTimeout(() => {
  console.log('Timeout callback');
}, 2000);

console.log('End');

在这个例子中,setTimeout的回调函数会在指定的时间后被放入任务队列中,而不会立即执行。

4. 异步编程

Promiseasync/await是现代JavaScript处理异步编程的重要工具,利用事件循环来管理异步操作的执行顺序。

async function fetchData() {
  console.log('Start fetching');

  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log('Data:', data);
  } catch (error) {
    console.error('Error:', error);
  }

  console.log('End fetching');
}

fetchData();

在这个例子中,await关键字使得代码看起来像是同步的,但实际上它会暂停函数的执行,等待fetch请求完成,然后再继续执行后续代码。

实际开发中的重要性

  1. 提高响应能力:通过异步编程,避免阻塞主线程,提高应用的响应速度和用户体验。
  2. 优化性能:使用事件循环机制,合理调度任务执行,避免不必要的性能开销。
  3. 简化代码结构:使用Promiseasync/await,使异步代码更加简洁和易读,减少回调地狱。

总结

在日常编码中,事件循环机制几乎无处不在。理解和善用这一机制,能够帮助开发者编写高效、响应迅速的JavaScript代码,并且在处理复杂的异步任务时也能游刃有余。无论是用户交互、网络请求还是定时任务,事件循环机制都是JavaScript开发者必须掌握的核心概念。

转载自:https://juejin.cn/post/7379151898568114216
评论
请登录