标准答案

  1. 回收依据:现代 JavaScript 引擎主要基于可达性分析。从全局对象、当前调用栈、闭包、DOM 引用等根对象出发,如果对象还能被访问到,就认为它可达,不能回收;如果不可达,就可以在后续 GC 中释放。
  2. 常见泄漏:前端常见内存泄漏包括定时器或事件监听没有清理、闭包长期持有大对象、缓存 Map 无限增长、DOM 节点移除后仍被 JS 引用、全局变量误挂载、组件卸载后异步回调仍然引用组件状态。
  3. 治理方式:资源不用时要解绑事件、清理定时器、取消请求、限制缓存大小,并在组件卸载或页面销毁时释放引用。仅仅把某个变量设为 null 不一定释放内存,前提是没有其它可达引用。
  4. WeakMap/WeakSet:WeakMap 的键是弱引用,适合给对象挂元数据或做对象级缓存;当对象本身不可达时,关联数据不会因为 WeakMap 而被强行保留。WeakMap 不可遍历,也没有 size,因为 GC 时机本身是不确定的。

题目解析

垃圾回收不需要背具体引擎算法,前端场景更关注可达性。只要对象还能从根对象访问到,它就不会被回收;对象不可达后,也只是“可以被回收”,不是立即释放。

内存泄漏的本质是“不该继续被引用的对象仍然可达”。事件监听、定时器、闭包、全局缓存、DOM 引用、异步回调都可能把已经无用的对象留在引用链上。

把变量设为 null 只是断开一个引用,不等于释放内存。只要别的闭包、Map、事件监听器或 DOM 属性还引用它,GC 仍然不会回收。

WeakMap/WeakSet 的价值是避免缓存本身阻止对象回收。它们适合对象元数据和缓存,但不能遍历、没有 size,也不适合作为普通业务列表存储。

代码示例

这类泄漏通常不是语法问题,而是资源生命周期没有收口。下面是组件销毁时清理副作用的思路。

JavaScript
function mount() {
  const controller = new AbortController()
  const timer = setInterval(() => {
    console.log('polling')
  }, 3000)

  function onResize() {
    console.log(window.innerWidth)
  }

  window.addEventListener('resize', onResize)
  fetch('/api/profile', { signal: controller.signal })

  return function unmount() {
    clearInterval(timer)
    window.removeEventListener('resize', onResize)
    controller.abort()
  }
}

常见误区

  • 误以为设置 obj = null 一定能释放内存。只有没有其他引用时才可能被回收。
  • 忽略事件监听和定时器中的闭包引用。
  • 把内存增长都当成泄漏,没区分正常缓存和不可释放引用。

作者信息