JavaScript 闭包

闭包(Closure):

内部函数可以访问外部函数作用域的变量,并且外部函数执行结束后,变量不会被销毁

快速判断是不是闭包:

满足两点就是闭包:

  1. 内层函数引用了外层函数的变量
  2. 内层函数在外层函数作用域之外执行

最简单的例子:

js 复制代码
function outer() {
  let msg = 'hello closure';
  // 内部函数 inner
  function inner() {
    console.log(msg); // inner 能访问 outer 的变量
  }
  return inner; // 返回内部函数
}

const fn = outer();
fn(); // hello closure

执行过程:

  1. outer() 执行,创建变量 msg、函数 inner
  2. outer 返回 inner,outer 函数执行完毕,正常情况下函数内变量会销毁
  3. 但 inner 形成闭包,依然持有 msg 的引用
  4. 调用 fn(),还能读取到 msg

使用场景(防抖、私有变量、Vue3 hooks)

面试话术: "闭包在实战里到处都是,最典型的有三个:"

  1. 防抖/节流(防抖为什么能记住 timer?) "我们写防抖的时候,外层函数里定义了一个 let timer = null,然后返回一个内层函数。因为闭包,内层函数每次执行都能访问到同一个 timer。所以第一次触发设了定时器,第二次触发才能 clearTimeout把它清掉。没有闭包,防抖根本写不出来。"
js 复制代码
function debounce(fn, delay) {
  let timer = null // 闭包变量

  return function (...args) {
    clearTimeout(timer) // 访问外层 timer
    timer = setTimeout(() => {
      fn.apply(this, args)
    }, delay)
  }
}
  1. 私有变量(怎么防止别人乱改数据?) "比如我想做一个计数器,我不希望别人直接 counter.count = 100 去改它。我就可以用闭包,把 count藏在外层函数里,只返回 increment、decrement 和 getCount 三个方法。这样外部只能通过我提供的方法来操作,数据就私有化了。"
js 复制代码
function createCounter() {
  // 1. 私有变量,外部访问不到
  let count = 0 

  // 2. 返回三个方法,它们都是闭包
  return {
    increment() {
      count++ // 3. 内部方法可以访问闭包里的 count
    },
    decrement() {
      count--
    },
    getCount() {
      return count // 4. 只提供读取方法,不提供直接修改的途径
    }
  }
}

const counter = createCounter()

counter.increment()
counter.increment()
console.log(counter.getCount()) // 2 原因:counter.increment() 执行两次

// 外部尝试直接修改,看看会发生什么?
counter.count = 100 // 只是给 counter 对象加了个新属性,根本没碰到里面的 count
console.log(counter.getCount()) // 依然是 2!(数据没被破坏)因为 getCount 只认闭包里的那个 count。
console.log(counter.count) // 100(但这个 100 是个假象,和闭包里的 count 毫无关系)
  1. Vue3 的组合式函数(useXXX 为什么能记住状态?) "我们写 Vue3 的 useCounter 或者 useRequest 时,里面写的 ref、reactive 变量,其实都处于闭包环境里。外层函数 useCounter() 执行完,按理说变量该销毁,但因为返回的方法(比如 increment)还在引用它们,所以这些状态一直活在内存里。Vue3 的 hooks 本质上就是闭包的集合。"
js 复制代码
export function useCounter() {
  const count = ref(0)

  function increment() {
    count.value++
  }

  return { count, increment }
}

increment 能访问 count,因为它是闭包。

useCounter 执行完了,count 还在,因为 increment 还引用着它。

闭包缺点

  • 内存泄漏风险:闭包会保留外层变量引用,变量不会被 GC 回收;大量使用容易占用内存。
  • 滥用闭包会让代码可读性变差、调试困难。

内存泄漏问题解决

不用了要及时断开引用

  1. 如果闭包返回的函数不用了,就把它置空(比如 fn = null)。
  2. 如果是组件里用的闭包(比如防抖),在组件卸载时(onUnmounted)一定要清掉定时器、解除事件监听。
  3. 不要无脑把大对象放在闭包外层,用不到就别放进去。
    其实现在现代浏览器(V8)很聪明,它只会保留闭包里真正用到的变量,所以不用过度恐慌,但该清理的时候一定要清理。
相关推荐
ZhengEnCi6 小时前
工科读研真的能抗学历贬值吗:研究生 vs 本科生的真实差距与选型判断
面试
Rain的Java大神之路8 小时前
国家数据集团校招、社招都开了:总部雄安,这些技术岗值得认真看看
java·后端·面试
Moment8 小时前
为什么这么多 Agent Harness 都选择 TypeScript
前端·后端·面试
时间的拾荒人9 小时前
CMake 实战指南:从基础语法到静态库/动态库制作与链接
开发语言·c++·面试
IT规划师1 天前
编程术语中的同步与异步:从"等不等"到工程取舍
后端·面试
考研保研资料分享1 天前
2022环境工程保研北师大:三校专业追问、论文贡献与择校复盘
面试
Rain的Java大神之路1 天前
面试官让你把 LLM、RAG、Agent、MCP 串起来讲清楚,90% 的 Java 工程师当场露馅
java·后端·面试
狂师1 天前
我开发了一款免费开源的测试开发刷题学习网站
面试·开源·测试
利威尔xu2 天前
Go IM 源码实战(14):本地缓存快 3000 倍,失效却难 10 倍
后端·缓存·微服务·面试·golang
利威尔xu2 天前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang