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)很聪明,它只会保留闭包里真正用到的变量,所以不用过度恐慌,但该清理的时候一定要清理。
相关推荐
程序员Sunday1 小时前
ES Module 和 CommonJS 有什么区别?循环依赖时会发生什么?
前端·javascript·面试
无敌贵点大王2 小时前
嵌入式软件面试经(自用)
c语言·stm32·单片机·学习·mongodb·面试·职场和发展
救救孩子把2 小时前
AI面试八股文-07_6-向量数据库索引详解
数据库·人工智能·面试·向量数据库·hnsw
程序 代码狂人2 小时前
数据开发面试【问答完整版|带深挖追问 + 扩展回答】
面试·职场和发展
江华森3 小时前
Web漏洞与中间件框架漏洞实战详解
面试
美味蛋炒饭.6 小时前
java八股面试(3)
java·开发语言·面试
考研保研资料分享6 小时前
2022食品跨申农业工程保研:中农、海大与华工考核复盘
面试
怕浪猫6 小时前
腾讯又交作业了,开源版 WorkBuddy 太夯了!这回是只粉色小章鱼Octop!
javascript·面试·agent
鸥梨菌Honevid6 小时前
UVM 面试高频:uvm_test_top 与 uvm_config_db 路径机制全解析
数据库·面试·职场和发展