一、事件循环(Event Loop)核心机制

1. 基础概念

JavaScript 是单线程语言,事件循环是其实现异步的核心机制。

执行流程:

  1. 调用栈(Call Stack):同步代码按顺序入栈执行
  2. Web API / 宿主环境:setTimeout、DOM事件、fetch等异步操作在这里等待
  3. 任务队列(Task Queue)
    • 宏任务(Macrotask):script整体代码、setTimeout、setInterval、setImmediate、I/O、UI渲染
    • 微任务(Microtask):Promise.then/catch/finally、queueMicrotask、MutationObserver、process.nextTick(Node)

2. 执行顺序(黄金法则)

同步代码 → 所有微任务 → 一个宏任务 → 所有微任务 → 下一个宏任务...

经典面试题输出:

console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => {
    console.log('3');
    Promise.resolve().then(() => console.log('4'));
});
console.log('5');
// 输出顺序:1 → 5 → 3 → 4 → 2

3. Node.js 事件循环(与浏览器差异)

Node 事件循环分 6 个阶段:

  • timers → pending callbacks → idle/prepare → poll → check → close callbacks

关键差异:

  • process.nextTick 优先级高于 Promise
  • 同一阶段内的宏任务会批量执行完,再清空微任务
  • Node 11+ 已与浏览器行为趋同

二、JS 异步演进史

1. 回调函数(Callback)

// 问题:回调地狱(Callback Hell)
getData(function(a) {
    getMoreData(a, function(b) {
        getMoreData(b, function(c) {
            // 嵌套过深,难以维护
        });
    });
});

缺点:嵌套深、错误处理难、难以并行

2. Promise

// 三种状态:pending → fulfilled / rejected(状态不可逆)
const promise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => resolve('成功'), 1000);
});

promise
    .then(res => console.log(res))
    .catch(err => console.error(err))
    .finally(() => console.log('总会执行'));

常用静态方法:

方法 特点 场景
Promise.all 全部成功才成功,一个失败就失败 并行请求,缺一不可
Promise.race 谁快用谁 超时控制
Promise.allSettled 等所有完成,收集结果 需要知道每个的结果
Promise.any 一个成功就成功,全失败才失败 容错降级

3. Generator + co

function* gen() {
    const a = yield fetch('/api/a');
    const b = yield fetch('/api/b');
    return [a, b];
}

async/await 的前身,需要 co 库自执行

4. async / await(ES2017,终极方案)

async function fetchData() {
    try {
        const a = await fetchA(); // 暂停等待
        const b = await fetchB(a); // 顺序执行
        return b;
    } catch (err) {
        console.error(err); // 统一错误捕获
    }
}

并行优化:

// ❌ 串行(慢)
const a = await fetchA();
const b = await fetchB();

// ✅ 并行(快)
const [a, b] = await Promise.all([fetchA(), fetchB()]);

三、React Native 中的异步场景

1. 核心异步 API

  • setTimeout / setInterval:定时器
  • fetch / axios:网络请求
  • AsyncStorage:本地存储(异步)
  • Animated:动画驱动
  • Bridge 通信:JS ↔ Native 异步消息机制

2. React Native 事件循环特点

  1. JS 线程独立:有独立的 JS 事件循环,不阻塞 UI 线程
  2. Bridge 异步:JS 与 Native 通过 Bridge 异步通信,批量处理
  3. requestAnimationFrame:与屏幕刷新同步(60fps)
  4. setImmediate / process.nextTick:Hermes 引擎下行为需注意

3. 常见性能陷阱

  • JS 线程阻塞:长时间计算导致动画掉帧 → 用 InteractionManager 或拆分任务
  • Bridge 拥堵:频繁跨线程通信 → 批量发送、减少序列化
  • 内存泄漏:组件卸载后未清除定时器/订阅 → useEffect 清理函数

四、高频面试题

Q1:Promise.then 为什么是微任务?

微任务在当前宏任务结束后、下一个宏任务前执行,保证尽快处理异步结果,避免被其他宏任务插队,提升响应性。

Q2:async 函数返回什么?

永远返回 Promise。非 Promise 值会被 Promise.resolve() 包装。

Q3:await 后面的代码是同步还是异步?

await 之前同步,之后是微任务回调。await 会让出线程,等待 Promise resolve 后,后续代码进入微任务队列。

Q4:Promise 构造函数是同步还是异步?

同步执行。只有 .then/.catch/.finally 的回调是异步微任务。

Q5:如何取消 Promise?

Promise 本身不可取消,可用:

  • AbortController(fetch 原生支持)
  • 封装可取消的 Promise(加 flag 标记)
  • Promise.race 配合超时 reject

Q6:React Native 中为什么 setTimeout 不准?

JS 线程被占用时事件循环延迟执行;Bridge 通信也可能引入延迟。高精度定时用 requestAnimationFrame 或 Native 模块。

Q7:宏任务微任务经典混合题

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2() {
    console.log('async2');
}

console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise(resolve => {
    console.log('promise1');
    resolve();
}).then(() => console.log('promise2'));
console.log('script end');

答案:

script start → async1 start → async2 → promise1 → script end → async1 end → promise2 → setTimeout

五、进阶考点

1. 事件循环与渲染时机

浏览器中:微任务 → 渲染更新 → 宏任务

  • 每次宏任务后不一定渲染,浏览器有自己的渲染节奏(约 16.6ms)
  • 微任务中修改 DOM 会合并到一次渲染

2. 任务调度优先级

process.nextTick > Promise.then > queueMicrotask > setTimeout > setImmediate

3. 并发模型总结

  • 并行(Parallelism):多线程同时执行(Web Worker / Native 多线程)
  • 并发(Concurrency):单线程通过事件循环交替处理多个任务
  • JS 是并发非并行(单线程),但宿主环境提供并行能力

4. React Native Hermes 引擎

  • 开启 Hermes 后事件循环行为基本一致
  • 启动更快、内存占用更低
  • 注意:某些非标准 API(如 process.nextTick)行为可能有差异

面试答题技巧: 回答事件循环问题时,先讲"同步代码全部执行完 → 清空微任务队列 → 取一个宏任务执行 → 再清空微任务"这个核心循环,再结合具体题目逐步推演,确保条理清晰。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐