浏览器的事件循环(Event Loop)是 JavaScript 执行模型的一部分,它负责协调异步任务的执行顺序。理解事件循环可以帮助我们更好地掌握 JavaScript 的异步机制,尤其是在处理如用户输入、定时器、网络请求等事件时。
-
基本概念:
事件循环是一个不断运行的循环,它检查是否有待处理的任务,并按顺序执行它们。事件循环的目的是确保 JavaScript 引擎能够以非阻塞的方式处理事件和异步操作。 -
执行栈(Call Stack):
执行栈是一个栈结构,用来存储当前正在执行的代码。当执行一个函数时,它会被压入栈中;执行完毕后会被弹出栈。 -
任务队列(Task Queue):
任务队列(也叫消息队列)是一个存放待执行任务的队列。当异步任务(如 setTimeout, 网络请求等)完成后,它们的回调函数会被推入任务队列,等待主线程空闲时执行。 -
事件循环的过程:
执行栈为空: 事件循环首先检查执行栈,如果栈为空,它就会从任务队列中取出一个任务。
执行任务: 取出的任务被压入执行栈,开始执行。
完成任务: 执行完当前任务后,事件循环会再次检查执行栈是否为空,若为空则从任务队列中取出下一个任务执行。
重复: 这一过程会不断重复,确保异步任务能够在合适的时机得到执行。 -
宏任务与微任务:
在事件循环中,任务队列被分为两种类型:宏任务和微任务。
宏任务(Macro Tasks):
包括 setTimeout、setInterval、I/O 操作、UI 渲染等。
每一轮事件循环中,都会先执行完一个宏任务队列中的所有任务,然后才会去执行微任务队列中的任务。
微任务(Micro Tasks):
包括 Promise 的 then 和 catch 回调、MutationObserver 等。
微任务的优先级比宏任务高,它们会在当前宏任务执行完后,执行完所有微任务队列中的任务后,再去执行下一个宏任务。
6. 执行流程:
执行栈为空时,事件循环开始。
从宏任务队列中取出第一个任务,压入执行栈中执行。
执行完宏任务后,检查微任务队列。
如果微任务队列中有任务,按照顺序依次执行微任务。
微任务队列执行完后,事件循环继续从宏任务队列中取出下一个任务。
7. 示例代码:
console.log("Start");setTimeout(() => {console.log("SetTimeout 1");
}, 0);Promise.resolve().then(() => {console.log("Promise 1");
});setTimeout(() => {console.log("SetTimeout 2");
}, 0);console.log("End");
执行顺序:
执行同步代码,先打印 “Start”。
遇到 setTimeout 和 Promise,将它们的回调分别加入宏任务队列和微任务队列。
执行完同步代码后,打印 “End”。
事件循环会先执行所有微任务(Promise 1),然后执行宏任务队列中的任务(SetTimeout 1 和 SetTimeout 2)。
输出结果:
Start
End
Promise 1
SetTimeout 1
SetTimeout 2
- 总结:
执行栈 负责执行当前同步代码。
任务队列 存放异步任务的回调函数,按顺序执行。
事件循环 保证了异步任务按照正确的顺序执行,先执行所有的微任务,再执行宏任务。
微任务的优先级高于宏任务,这也是为什么在上面的例子中 Promise 1 会先被打印出来。
理解事件循环能够帮助你更好地理解 JavaScript 中的异步编程和如何避免一些常见的陷阱,比如任务队列的执行顺序、微任务与宏任务的执行顺序等。