前言
JavaScript 是一种单线程的编程语言,这意味着它一次只能执行一个任务。这一特性源于 JavaScript 最初设计的目标之一:与浏览器的交互。当 JavaScript 在浏览器中运行时,它负责处理用户的交互、DOM 操作以及与服务器的通信等任务。这也意味着在执行 JavaScript 代码的过程中,如果某个任务阻塞了(例如,执行了一个耗时很长的操作或者在等待某些资源),整个执行流程都会被阻塞,其他任务无法继续执行。为了解决这个问题,JS中出现了同步和异步。在讲 JavaScript 任务执行机制前,先要了解一下什么是同步任务与异步任务。
同步任务:即主线程上的任务,按照顺序由上⾄下依次执⾏,当前⼀个任务执⾏完毕后,才能执⾏下⼀个任务。同步操作会阻塞代码的执行,直到操作完成才会继续执行下一步。
异步任务:不进⼊主线程,⽽是进⼊任务队列的任务,执行完毕之后会产生一个回调函数,并且通知主线程。当主线程上的任务执行完后,就会调取最早通知自己的回调函数,使其进入主线程中执行。
Event Loop (事件循环) 是什么?
事件循环 Event Loop 又叫 事件队列
Event Loop(事件循环)是JavaScript运行时环境中的一部分,负责管理和协调执行代码、处理事件以及执行任务队列。它是实现JavaScript异步机制的核心。换句话说就是 JS 是打工人,Event Loop 是公司的规章制度。打工人按照规章制度进行执行操作。
Event Loop (事件循环) 的运行机制以及工作原理是什么?
工作原理
- 执行栈(Execution Stack): 这是一个存储执行上下文的地方,代码执行时会按照先进后出的原则在执行栈中添加和移除执行上下文。
- 任务队列(Task Queue): 当异步任务完成时,它会被放入任务队列中。任务队列中的任务等待被执行。
- 事件触发线程: 负责监听事件,当事件发生时,将相应的回调函数放入任务队列。
运行机制
- 执行栈会不断地执行同步任务,如果遇到异步任务,会将其放入任务队列。
- 当执行栈为空时,Event Loop会检查任务队列。
- 如果任务队列中有任务,Event Loop会将队列中的第一个任务移到执行栈中执行。
- 这个过程不断重复,使得异步任务得以执行。
# 微任务、宏任务是什么?哪些是微任务 哪些是宏任务呢?
微任务(Microtask)和宏任务(Macrotask)是与Event Loop(事件循环)密切相关的概念,用于描述在JavaScript中异步任务的执行顺序。也就是说 不管是微任务还是宏任务都属于Js 异步任务的一个范畴
微任务(Microtask):微任务是一类相对较小、相对优先级较高的任务,它们在当前任务执行完成后、下一个事件循环开始之前执行
触发时机 | 任务类型 |
---|---|
Promise的then 回调被触发 |
微任务 |
Promise的catch 回调被触发 |
微任务 |
Promise的finally 回调被触发 |
微任务 |
MutationObserver的回调被触发 | 微任务 |
宏任务(Macrotask):宏任务是一类相对较大、相对优先级较低的任务,它们在当前任务执行完成后,下一个事件循环开始时执行。宏任务包括以下几种:
触发时机 | 任务类型 |
---|---|
setTimeout的回调函数被触发 | 宏任务 |
setInterval的回调函数被触发 | 宏任务 |
setImmediate(仅在Node.js环境)的回调函数被触发 | 宏任务 |
requestAnimationFrame的回调函数被触发 | 宏任务 |
I/O 操作完成(例如读取文件、网络请求) | 宏任务 |
用户交互(例如点击事件) | 宏任务 |
关于宏任务和微任务 容易理解错的地方
理解宏任务和微任务的概念对于JavaScript异步编程至关重要,但有一些容易混淆的点,下面是一些常见的误解:
- 微任务比宏任务更快: 这是一个常见的误解。微任务和宏任务的速度并不是绝对的,而是取决于执行上下文和任务队列的状态。微任务通常在当前任务执行完成后立即执行,而宏任务则在下一轮事件循环开始时执行。但是,在某些情况下,微任务可能会在宏任务之前执行。
- setTimeout 是微任务:
setTimeout
中的回调实际上是宏任务,而不是微任务。它会在指定的时间间隔之后被添加到宏任务队列,而不是微任务队列。 - Promise 的回调函数是微任务: 这是正确的,Promise 的
then
、catch
和finally
回调确实是微任务。但是,Promise 的构造函数中的执行是同步的,而 Promise 的状态变化后的回调是微任务。 - 异步操作总是产生微任务: 不是所有的异步操作都是微任务,有些异步操作是宏任务。例如,
setTimeout
、setInterval
、用户交互事件、I/O 操作等都是宏任务。 - 同一轮事件循环中,微任务总是在宏任务之前执行: 在一轮事件循环中,宏任务和微任务的执行顺序是有规律的,但并不是微任务总是在宏任务之前执行。微任务队列在执行过程中可能会有新的微任务添加,因此微任务和宏任务的执行顺序可能会有变化。
总体而言,理解这些概念的关键在于熟悉事件循环的执行顺序,并且在实际编码中要根据需求来合理使用宏任务和微任务。
最后给你们留下一个小问题 大家可以在评论区留下你的答案以及为什么
javascript
console.log('Start');
setTimeout(() => {
console.log('Timeout callback 1');
}, 0);
Promise.resolve().then(() => {
console.log('Promise resolve 1');
setTimeout(() => {
console.log('Timeout callback 2');
}, 0);
Promise.resolve().then(() => {
console.log('Promise resolve 2');
});
});
console.log('End');