Javascript:什么是事件循环与异步
一. 基本概念
1. JavaScript 是单线程的
JavaScript 的执行是单线程的,也就是说,同一时间只能做一件事。但浏览器或 Node.js 提供了 异步 API(比如 setTimeout
, fetch
, fs.readFile
等),让你可以“安排”一些代码以后再执行,而不会阻塞当前线程。
2. 事件循环
事件循环是 JavaScript 的“调度系统”,负责管理同步和异步代码的执行顺序。
它大致按照以下流程工作:
1. 执行栈
这是 JavaScript 正在执行的代码。当你调用一个函数,它就被压入这个栈;函数执行完毕后,从栈中弹出。
2. 任务队列
当异步操作(比如定时器、I/O、事件监听)完成时,相关的回调会被加入这个队列。
3. 微任务队列
比任务队列更“快”的一个队列,用来处理像 Promise.then()
、MutationObserver
这样的任务。
事件循环机制(简化版):
-
执行同步代码(压入并清空 Call Stack)
-
清空所有微任务队列
-
执行一个任务队列(比如
setTimeout
的回调) -
重复这个循环
二. 异步
异步方式 | 加入队列类型 | 特点 |
---|---|---|
setTimeout(fn) | 宏任务队列 | 回调最早在指定时间后执行 |
Promise.then() | 微任务队列 | 更快,通常在当前宏任务结束后立即执行 |
fetch() | 微任务(响应处理) | 异步网络请求 |
async/await | 基于 Promise | 语法糖,写法像同步代码 |
示例解释 :
console.log("1");
setTimeout(() => {
console.log("2");
}, 0);
Promise.resolve().then(() => {
console.log("3");
});
console.log("4");
输出顺序是:
1
4
3
2
解释:
-
1
、4
是同步,直接执行。 -
3
是微任务,在同步执行完后立刻执行。 -
2
是宏任务(setTimeout
),等事件循环下一轮再执行。
三. 图解异步
图解事件循环核心概念
事件循环运行流程图(简化版)
用这个例子来对照
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
Promise.resolve().then(() => {
console.log("C");
});
console.log("D");
输出顺序:
A // 同步
D // 同步
C // 微任务(执行在同步之后)
B // 宏任务(下一轮事件循环)
四. 示例
例题:请判断下列代码的输出顺序
console.log('A');
setTimeout(() => {
console.log('B');
}, 0);
Promise.resolve().then(() => {
console.log('C');
});
console.log('D');
选项:
A. A D B C
B. A D C B
C. A C D B
D. A C B D
提示思路:
1. console.log('A')
和 console.log('D')
是同步代码,立刻执行。
2. setTimeout(..., 0)
是宏任务,排队等待下一轮事件循环。
3. Promise.then()
是微任务,在当前轮同步代码之后、宏任务之前执行。
解析:
1. 同步代码:直接进入执行栈
console.log('A'); // 输出 A
setTimeout(...); // 注册一个宏任务,暂时不执行
Promise.resolve().then(...); // 注册一个微任务,暂时不执行
console.log('D'); // 输出 D
此时输出:
A
D
2. 清空微任务队
Promise.resolve().then(() => {
console.log('C');
});
-
微任务在同步代码执行完毕后立刻执行。
-
所以接着输出:
C
3. 执行宏任务队列
setTimeout(() => {
console.log('B');
}, 0);
-
宏任务要等当前事件循环结束才会执行。
-
所以最后输出:
B
最终输出顺序: A
D
C
B