JavaScrip事件循环机制,你了解多少?

JavaScrip事件循环机制,你了解多少?

JavaScript的事件循环机制是其处理异步操作的核心机制,它使得JavaScript能够在单线程的环境下处理各种异步任务,比如处理用户输入、网络请求、定时器等,以下是详细介绍:

执行栈与任务队列

  • 执行栈:也叫调用栈,是一种后进先出的数据结构。当JavaScript引擎执行代码时,会将函数调用、变量声明等操作按照顺序压入执行栈,函数执行完后再从栈顶弹出。比如下面的代码:
function add(a, b) {
    return a + b;
}
function multiply(a, b) {
    return add(a, b) * 2;
}
multiply(3, 4);

在执行这段代码时,JavaScript引擎会先将multiply函数压入执行栈,在执行multiply函数过程中,又会将add函数压入执行栈,add函数执行完返回结果后从栈顶弹出,multiply函数继续执行并返回最终结果,然后multiply函数也从执行栈弹出。

  • 任务队列:用于存放异步任务的回调函数。异步任务不会立即进入执行栈执行,而是在满足一定条件后,将其回调函数放入任务队列。任务队列分为宏任务队列和微任务队列。常见的宏任务有setTimeoutsetIntervalI/O操作、UI渲染等;常见的微任务有Promise.thenMutationObserver等。

事件循环过程

  1. 执行同步代码:JavaScript引擎首先会执行执行栈中的同步代码,按照代码顺序依次执行,将函数调用等操作压入执行栈,执行完后弹出。
  2. 检查微任务队列:当执行栈中的同步代码执行完后,JavaScript引擎会立即检查微任务队列,将微任务队列中的回调函数依次放入执行栈执行,直到微任务队列为空。
  3. 执行宏任务:微任务队列清空后,从宏任务队列中取出一个宏任务放入执行栈执行。执行完这个宏任务后,再次检查微任务队列,将其中的任务全部执行完,然后再从宏任务队列中取一个宏任务执行,如此循环,这就是事件循环。

以下是一个简单的代码示例来展示事件循环:

console.log('start');

setTimeout(() => {
    // 这是一个宏任务的回调函数
    console.log('setTimeout');
}, 0);

Promise.resolve().then(() => {
    // 这是一个微任务的回调函数
    console.log('Promise.then');
});

console.log('end');

在这段代码中:

  • 首先,console.log('start')console.log('end')是同步代码,会立即执行,所以先输出startend
  • 然后,setTimeout是宏任务,虽然设置的延迟时间为0,但它还是会在本轮事件循环的同步代码执行完后,下一轮事件循环时才执行,所以不会立即输出setTimeout
  • Promise.resolve().then是微任务,会在本轮事件循环的同步代码执行完后,在执行下一个宏任务之前执行,所以会先输出Promise.then
  • 最后,下一轮事件循环时,才会执行setTimeout中的回调函数,输出setTimeout

总的来说,事件循环机制是JavaScript实现异步编程的基础,它通过执行栈、任务队列以及不断循环的机制,让JavaScript能够有条不紊地处理各种同步和异步任务,保证了程序的正常运行和响应。

浏览器中的事件循环机制和Node.js中的有什么不同?
浏览器和Node.js中的事件循环机制在基本原理上是相似的,但在具体实现和一些细节方面存在差异,以下是详细介绍:

宏任务类型

  • 浏览器环境:常见的宏任务包括setTimeoutsetIntervalI/O操作、UI渲染、requestAnimationFrame等。浏览器需要处理页面渲染、用户交互等操作,所以有专门的UI渲染任务和requestAnimationFrame用于动画相关的处理。例如:
// 在浏览器中使用requestAnimationFrame
function animate() {
    // 执行动画相关操作
    console.log('动画帧执行');
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
  • Node.js环境:宏任务类型有setTimeoutsetIntervalI/O操作、setImmediate等。setImmediate是Node.js特有的宏任务,它会在当前轮次的I/O操作完成后,下一轮I/O操作开始前执行。例如:
// 在Node.js中使用setImmediate
setImmediate(() => {
    console.log('setImmediate执行');
});

微任务类型

  • 浏览器环境:主要的微任务有Promise.thenMutationObserver等。MutationObserver用于监听DOM变化,是浏览器特有的微任务类型。例如:
// 在浏览器中使用MutationObserver
const observer = new MutationObserver(() => {
    console.log('DOM变化');
});
const target = document.getElementById('someElement');
observer.observe(target, { attributes: true });
  • Node.js环境:微任务有Promise.thenprocess.nextTick等。process.nextTick是Node.js中一个非常特殊的微任务,它的优先级比其他微任务更高,会在当前操作完成后,事件循环进入下一个阶段之前立即执行。例如:
// 在Node.js中使用process.nextTick
process.nextTick(() => {
    console.log('process.nextTick执行');
});

事件循环阶段

  • 浏览器环境:浏览器的事件循环相对简单,大致分为执行栈执行、微任务队列执行、宏任务队列执行、UI渲染等几个阶段,按照顺序循环执行。
  • Node.js环境:Node.js的事件循环更加复杂,有多个阶段,包括timers阶段(处理setTimeoutsetInterval)、I/O阶段、idleprepare阶段、poll阶段(主要处理I/O操作)、check阶段(执行setImmediate)、close阶段等。每个阶段都有其特定的任务和功能,事件循环会按照顺序依次进入各个阶段执行相应的任务。例如:
// 在Node.js中展示不同阶段的执行顺序
setTimeout(() => {
    console.log('setTimeout在timers阶段执行');
}, 0);

setImmediate(() => {
    console.log('setImmediate在check阶段执行');
});
// 可能会先输出setTimeout的内容,也可能先输出setImmediate的内容,取决于事件循环的具体情况

执行顺序差异

  • 浏览器环境:一般来说,在浏览器中,如果setTimeoutPromise.then同时存在,先执行完同步代码,然后执行微任务队列中的Promise.then,再执行宏任务队列中的setTimeout
  • Node.js环境:在Node.js中,如果setTimeoutsetImmediateprocess.nextTick同时存在,process.nextTick会在当前操作完成后立即执行,然后可能是setTimeouttimers阶段执行,也可能是setImmediatecheck阶段执行,这取决于具体的情况和代码执行的时机。例如:
// 在Node.js中展示执行顺序
setTimeout(() => {
    console.log('setTimeout');
}, 0);

setImmediate(() => {
    console.log('setImmediate');
});

process.nextTick(() => {
    console.log('process.nextTick');
});
// 一般会先输出process.nextTick,然后可能setTimeout先输出,也可能setImmediate先输出

总的来说,浏览器和Node.js中的事件循环机制都是为了实现异步编程,但由于它们的应用场景不同,在具体的任务类型、事件循环阶段和执行顺序等方面存在一些差异。

你可能感兴趣的:(大白话前端面试题,JavaScript,学习笔记,javascript,前端)