彻底搞懂 JavaScript 闭包:原理、陷阱与内存优化全解析

一、闭包的定义

闭包(Closure) 是指 有权访问另一个函数作用域中的变量的函数

简单来说,就是函数可以"记住"定义时的作用域,而不是调用时的作用域

scss 复制代码
function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  }
}

const fn = outer();
fn(); // 1
fn(); // 2

这里 inner 就是闭包,它可以访问 outer 的变量 count


二、闭包的形成条件

闭包产生通常需要三个条件:

  1. 函数嵌套:有函数内部函数。
  2. 内部函数引用外部变量
  3. 外部函数返回内部函数或将内部函数传出

三、闭包的作用

  1. 保护变量私有化(封装)
javascript 复制代码
function createCounter() {
  let count = 0;
  return {
    increment() { count++; return count; },
    decrement() { count--; return count; }
  }
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.decrement()); // 0

外部无法直接访问 count,只能通过提供的方法操作。

  1. 函数工厂 / 动态生成函数
javascript 复制代码
function multiplyBy(n) {
  return function(x) {
    return x * n;
  }
}
const double = multiplyBy(2);
console.log(double(5)); // 10
  1. 保持状态(常用于异步、事件回调)

四、闭包的面试常考点

1. 循环中的闭包问题

javascript 复制代码
for (var i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
// 输出:3 3 3

原因var 是函数作用域,闭包引用的是同一个 i
解决方案

  • 使用 let(块级作用域):
javascript 复制代码
for (let i = 0; i < 3; i++) {
  setTimeout(function() { console.log(i); }, 100);
}
// 输出:0 1 2
  • 或使用立即执行函数(IIFE):
css 复制代码
for (var i = 0; i < 3; i++) {
  (function(i){
    setTimeout(function() { console.log(i); }, 100);
  })(i);
}

2. 闭包与内存泄漏

闭包会 引用外部作用域变量 ,可能导致 垃圾回收无法释放

  • 避免过度使用全局闭包。
  • 在不需要时,手动清理引用。

3. 面试题示例

题1:输出结果?

ini 复制代码
function makeFunc() {
  let name = "Yo Yo";
  return function() {
    console.log(name);
  }
}
const func = makeFunc();
func();

答案Yo Yo

因为闭包保持了对 name 的引用。

题2:循环闭包输出?

scss 复制代码
var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs[i] = function() { console.log(i); };
}
funcs[0](); funcs[1](); funcs[2]();

答案3 3 3

var 是函数作用域,闭包引用的是同一个 i

题3:使用闭包实现私有变量

javascript 复制代码
function Counter() {
  let count = 0;
  this.increment = function() { count++; return count; }
  this.decrement = function() { count--; return count; }
}
const c = new Counter();
console.log(c.increment()); // 1

题4:闭包结合 setTimeout

css 复制代码
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0);
}

答案0 1 2

let 作用域绑定每次循环的 i


五、闭包引起内存泄漏问题

1 为什么闭包会造成内存无法释放?

闭包之所以"强大",是因为它让内部函数持续引用 外部作用域中的变量。

但也正因为这个引用存在,即使外层函数已经执行完毕,外层变量仍然被保存在内存中,不能被垃圾回收(GC)释放。

示例

javascript 复制代码
function outer() {
  let bigData = new Array(1000000).fill('数据');
  return function inner() {
    console.log(bigData.length);
  }
}
const fn = outer();
// outer 已经执行完毕,但 bigData 仍被 inner 引用,无法回收

这里 bigData 占用大量内存,而 fn 一直持有引用,GC 就不会释放。


2 在不需要时,如何"手动清理引用"

关键思路:让闭包不再引用外部变量

✅ 方式 1:将闭包变量置为 null

javascript 复制代码
function outer() {
  let bigData = new Array(1000000).fill('数据');
  return function inner() {
    console.log(bigData.length);
    bigData = null; // 手动解除引用
  }
}

const fn = outer();
fn(); // 使用一次

调用 fn 后手动将 bigData = null,让 GC 能识别为可回收。


✅ 方式 2:将闭包函数引用置为 null

php 复制代码
let fn = (function() {
  let data = { name: 'Yo Yo' };
  return function() {
    console.log(data);
  }
})();

fn();      // 使用
fn = null; // 手动解除引用,闭包随之释放

一旦 fn 没有被引用,整个闭包作用域都会被 GC。


✅ 方式 3:避免不必要的全局闭包

不要让闭包函数挂在全局对象上,比如:

javascript 复制代码
window.fn = (function() {
  let cache = [];
  return function() { cache.push(Date.now()); }
})();

改进:

javascript 复制代码
(function() {
  let cache = [];
  document.body.addEventListener('click', function() {
    cache.push(Date.now());
  });
})();

只在局部使用闭包,事件解绑或模块销毁时自然会释放。


✅ 方式 4:组件或页面卸载时清理(在框架中)

比如 React、Vue 中使用闭包保存状态时,在组件卸载阶段清理:

React 示例

javascript 复制代码
useEffect(() => {
  let timer = setInterval(() => console.log('run'), 1000);
  return () => clearInterval(timer); // 清理引用
}, []);

如果闭包里引用了外部变量(如 DOM、定时器),必须在 cleanup 阶段清理,否则内存会持续增长。


3、判断闭包导致的内存泄漏的常见迹象

  • 控制台内存面板中,执行某函数后内存不下降;
  • 页面关闭某功能后,内存占用仍持续增加;
  • 浏览器 Performance 记录中,Detached DOM 节点未被释放。

总结要点

  • 闭包 = 函数 + 对外部变量的引用

  • 好处

    • 数据私有化
    • 保存状态
    • 动态函数生成
  • 注意

    • 循环变量问题
    • 内存泄漏
    • 不要滥用全局闭包
相关推荐
布茹 ei ai21 小时前
5、基于 GEE 的 Sentinel-1 SAR 地震滑坡变化检测系统:2022 泸定地震案例
javascript·sentinel·遥感·gee·云平台
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ21 小时前
java实现登录:多点登录互踢,30分钟无操作超时
java·前端
一字白首21 小时前
Vue Router 进阶,声明式 / 编程式导航 + 重定向 + 404 + 路由模式
前端·javascript·vue.js
d111111111d21 小时前
C语言中static修斯局部变量,全局变量和函数时分别由什么特性
c语言·javascript·笔记·stm32·单片机·嵌入式硬件·学习
广州华水科技21 小时前
单北斗变形监测在水库安全中的应用与维护该如何实施?
前端
GIS好难学21 小时前
0帧起手《Vue零基础教程》,从前端框架到GIS开发系列课程
前端·vue.js·前端框架
李瑞丰_liruifengv21 小时前
使用 Claude Agent SDK 写一个 DeepResearch Agent
javascript·aigc·agent
行走的陀螺仪21 小时前
重绘和重排怎么触发?怎么优化?
前端·css·性能优化·css3·浏览器原理
尘世中一位迷途小书童21 小时前
项目大扫除神器:Knip —— 将你的代码库“瘦身”到底
前端·架构·代码规范