Skip to content

闭包

定义

官方定义 闭包 = 函数 + 函数能访问的词法作用域

一句话总结 闭包 = 内部函数 + 引用外部函数变量 + 外部函数执行完毕不销毁

示例

js

function outer(){
  let count = 0;
  
  function inner(){
    count++;
    console.log(count);
  }

  return inner;
}
  const fn = outer();

  fn(); // 1
  fn(); // 2
  fn(); // 3
  1. inner 引用了 outer 的 count
  2. outer 执行完不会销毁作用域
  3. fn 每次调用都操作同一个 count

原理

  1. 词法作用域 函数在哪里定义,就能访问哪里的变量,和在哪执行无关
  2. 作用域不销毁 正常函数执行完 → 作用域销毁 闭包函数 → 作用域被保留(因为被引用)
  3. 垃圾回收机制 没有引用 → 被回收 闭包持有引用 → 不回收

用途

1.私有化变量(模拟私有属性)

js
function createPerson(){
  let name="睡觉";

  return {
    getName: ()=>name,
    setName: (newName)=>{
      name = newName;
    }
  }
}

const p = createPerson();
console.log(p.getName());//睡觉
p.setName("嘻嘻");
console.log(p.setName());

console.log(p.name);//undefined

2.缓存/持久化数据

闭包可以让变量常驻内存,实现缓存。

3.函数柯里化

js
function add(a){
  return function(b){
    return a+b;
  }
}

const add = add(10);
console.log(add(1));//11

4.解决循环中异步取值问题

js
//输出都是5
for(var i=0;i<5;i++){
  setTimeout(()=>console.log(i),1000);
}

//闭包
for(var i=0;i<5;i++){
  ((j)=>{
    setTimeout(()=>console.log(j),1000);
  })(i);
}

优缺点

优点

  • 私有化变量,避免全局污染
  • 可以缓存数据
  • 函数式编程核心(柯里化、工厂函数)
  • 模块化基础

缺点

  • 内存泄漏风险(变量常驻)
  • 过度使用会影响性能
  • 调试困难(作用域链复杂)

面试常问

  1. 什么是闭包
  • 闭包是内部函数引用了外部函数作用域的变量;
  • 即使外部函数执行完毕,内部函数依然可以访问外层作用域变量;
  • 本质:函数 + 词法作用域的组合,作用域不会被 GC 回收。
  1. 闭包产生的条件?
  • 函数嵌套
  • 内部函数引用外层函数的变量/参数
  • 内部函数在外层作用域之外被调用
  1. 闭包的作用 / 应用场景?
  • 实现变量私有化
  • 保存局部变量,做数据缓存、状态持久化
  • 函数柯里化,高阶函数封装
  • 解决循环异步定时器变量捕获
  • 模块化思想、防抖 / 节流 / 计数器底层都用到闭包。
  1. 闭包缺点
  • 外层作用域变量常驻内存,无法被垃圾回收
  • 容易造成内存泄漏
  • 滥用会导致代码可读性变差,性能下降
  1. 闭包为什么会造成内存泄漏?
  • 正常函数执行完,局部作用域会销毁、变量被 GC 回收;
  • 但闭包会持续持有外层变量的引用,浏览器无法回收这块内存,长期堆积就会引发内存泄漏。
  1. 怎么解决闭包内存泄漏?
  • 手动切断引用:把接收闭包函数的变量赋值为 null
  • 尽量减少不必要的全局闭包
  • 及时销毁定时器、事件监听等闭包场景
js
let fn = outer();
fn = null; // 解除引用,等待GC回收
  1. 循环 + setTimeout 经典闭包题
js
for(var i=0;i<5;i++){
  setTimeout(()=>{
    console.log(i)
  },1000)
}
输出:5 5 5 5 5
原因:
var 没有块级作用域,全局只有一个 i;
定时器回调是闭包,延迟执行时循环早已结束,i 最终变成 5。
三种解决方式:
方案 1:用 let 块级作用域(最简单)
方案 2:立即执行函数 (IIFE) + 闭包保存每一轮变量
方案 3:定时器第三个参数传参
  1. let/var 对闭包的影响?
  • var 函数级作用域,循环共享同一个变量,闭包会捕获同一个值;
  • let 块级作用域,每次循环都会生成独立绑定,每个闭包捕获独立变量,不会错乱。
  1. 作用域、作用域链、闭包的关系
  • 作用域:变量的可访问范围;
  • 作用域链:变量查找的逐级规则;
  • 闭包:利用作用域链,跨作用域保留变量引用。