面试备战录

121 阅读4分钟

1、哪些情况会导致内存泄漏?如何排查和解决?

答:内存泄漏:本该被垃圾回收(GC) 释放的内存,因为仍然被引用着而无法释放。常见场景:

  • 全局变量:不小心在函数里漏写var/let/const,变量变成全局的;会持久存在,GC无法回收。
function foo() {
  leak = 'I am a global var'; // 忘记声明
}
  • 定时器/回调未清理:setInterval没有clearInterval;绑定的事件监听器addEventListener没卸载。
const timer = setInterval(() => console.log('leak'), 1000);
  • 闭包使用不当:函数引用了外部变量,但函数一直存在,导致外部变量无法回收。
function outer() {
  let bigData = new Array(1000000).fill('xxx');
  return () => console.log(bigData[0]); // 持有引用
}
const leakFn = outer();
  • DOM引用未释放:移除了DOM元素,但JS中还保留了变量引用。
let btn = document.getElementById('button');
document.body.removeChild(btn);
// btn = null 清除,Vue和React框架内一般或自动清理掉,但定时器要手动清理
  • 缓存/数据结构未清理:使用Map/Set保存了大量数据,但从不清理。单页应用(SPA)里路由切换,组件没卸载干净。
const cache = new Map();
cache.set('key', new Array(1000000));

// cache.clear(); 清理
// cache = null;清理
  • 事件总线/发布订阅未解绑:比如Vue/React中的EventEmitter/PubSub没有off

注:

  • 基本类型:在Map/Set里存的是值,不存在GC回收的问题。
  • 引用类型:在Map/Set里存的是强引用,不手动清理就不会被回收。
  • 解决办法:用WeakMap/WeakSet,它们存的是弱引用,对象没引用后会被GC回收。

如何排查内存泄漏

  • Chrome DevTools → Performance/Memory
    • 打开Performance,录制运行 → 查看内存曲线是否持续上升。
    • 使用 Memory → Heap snapshot,对比前后两次快照,看哪些对象一直存在。
  • Timeline → Memory Profiler
    • 长时间运行时观察堆内存变化,是否出现 “锯齿状”(正常 GC)还是只涨不降(泄漏)。
  • Performance API
    • 使用performance.memory.usedJSHeapSize监控内存占用(仅Chrome提供)。
  • 断点排查
    • 如果知道大概泄漏位置,可以在相关对象销毁时打断点,检查引用链。

解决思路

  • 全局变量 → 避免直接挂到window,使用局部作用域或模块化。
  • 闭包 → 谨慎使用,确保不必要的引用及时解除。
  • DOM/事件 → 在组件销毁时removeEventListener、置null,避免引用残留。
  • 定时器 → 使用clearTimeout/clearInterval,或组件卸载时清理。
  • 缓存 → 使用WeakMap/WeakSet存储对象,避免强引用。
  • 框架组件 → 确保在生命周期里清理资源(React/Vue等都有对应钩子)。
  • 工具 → 定期使用Chrome DevTools快照检查。

2、Promise 的基本原理是什么?

答:Promise异步编程的一种解决方案,它的核心思想是维护一个状态机:初始状态(pending)成功状态(fulfilled)失败状态(rejected)

特点

  • 状态不可逆:从pending → fulfilledpending → rejected,一旦确定就不能再改变。
  • 回调收集:通过.then()注册回调,在状态改变后依次执行。
  • 链式调用:.then()会返回一个新的Promise,从而支持链式操作。
  • 异步执行:Promise的回调会被放到微任务队列,不会阻塞主线程。

3、Promise.all / Promise.race / Promise.any / Promise.allSettled 区别?

答:

  • Promise.all:所有Promise成功才成功;有一个失败立即失败;成功:按顺序返回数组;任何一个失败 → 整个失败。应用场景:并发请求依赖所有数据(比如首页多个接口数据)。
  • Promise.race:谁先结束(无论成功/失败),返回第一个结果(值或错误),第一个失败就失败;典型场景:请求超时控制(竞速)
  • Promise.any:任意一个成功就成功,返回第一个成功的结果,如果全部失败 → 返回AggregateError;典型场景:多 CDN / 多资源兜底,只要一个成功即可
  • Promise.allSettled:等待全部结束(不论成功/失败),每个结果对象 {status, value/reason},不会中断;典型场景:批量请求结果统计或展示

Promise.anyPromise.race的区别:race只看谁先结束,可能是 reject;any只关心谁先成功,所有失败才报错。 allallSettled的区别:all遇到错误会短路,剩下的不会继续等;allSettled会等到所有任务完成,不管成败。

4、async / await 的底层原理?相当于什么语法糖?

答:async函数始终返回一个Promiseawait表达式会 暂停async函数的执行,等待Promise resolve,然后继续执行。底层实现依赖Promise + 微任务队列。换句话说:async/await 是基于Promise的语法糖,让异步写法看起来像同步。

底层执行原理

  • 调用async函数:返回一个Promise,内部代码立即执行到第一个await表达式。
  • 遇到await
    • 暂停当前async函数的执行(不阻塞主线程)
    • await后面的表达式(后面所有的代码,包含同步代码)会被Promise.resolve()包装
    • Promise完成后,把后续代码放入微任务队列执行
  • 继续执行
    • 微任务队列调度时,async函数继续从暂停点执行
    • 遇到下一个await再重复这个过程

可以理解为:JS引擎async函数拆成若干个Promise 链 + 微任务调度