JavaScript 闭包:函数背后的“背包”

🎒 JavaScript 闭包:函数背后的"背包"

🤔 什么是闭包?

官方定义

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

通俗解释

想象一个函数是一个 ,他出生时背了一个背包 (Scope/作用域)。

这个背包里装着他出生时周围环境中的所有变量。

即使这个人走到了世界的尽头(全局环境),或者他的父母(外部函数)已经去世(执行完毕),他依然背着那个背包,可以随时拿出里面的东西使用。

一句话总结
闭包 = 函数 + 函数能访问到的自由变量(背包里的东西)。


📂 目录

  1. [🎒 核心原理:为什么会有闭包?](#🎒 核心原理:为什么会有闭包?)
  2. [🛠️ 闭包的三大核心作用](#🛠️ 闭包的三大核心作用)
  3. [💻 代码实战:经典案例解析](#💻 代码实战:经典案例解析)
  4. [⚠️ 双刃剑:内存泄漏与性能](#⚠️ 双刃剑:内存泄漏与性能)
  5. [💡 总结](#💡 总结)

1. 🎒 核心原理:为什么会有闭包?

要理解闭包,首先要理解 JS 的词法作用域(Lexical Scoping)垃圾回收机制

正常情况:函数执行完,变量销毁

javascript 复制代码
function outer() {
  let a = 10;
  console.log(a);
}
outer(); // 10
// 函数执行完毕,a 被垃圾回收器回收,内存释放。

特殊情况:内部函数引用了外部变量

javascript 复制代码
function outer() {
  let a = 10;
  function inner() {
    console.log(a); // inner 引用了 outer 的变量 a
  }
  return inner; // 将 inner 返回出去
}

const myFunc = outer();
myFunc(); // 10

发生了什么?

  1. outer 执行完毕,按理说 a 应该被销毁。
  2. 但是,inner 函数被返回并赋值给了 myFunc
  3. inner 的定义中包含了对 a 的引用。
  4. JS 引擎发现:"嘿,inner 还在外面被人拿着呢,它还要用 a,所以我不能销毁 a!"
  5. 于是,a 所在的内存空间被保留下来,形成了闭包

2. 🛠️ 闭包的三大核心作用

闭包不仅仅是理论,它在实际开发中有三个非常强大的用途:

✅ 作用一:数据封装与私有变量(模拟私有属性)

在 ES6 Class 出现之前,JS 没有真正的"私有变量"。闭包是实现模块化和数据隐藏的核心手段。

场景 :创建一个计数器,外部只能调用 increment,无法直接修改 count

javascript 复制代码
function createCounter() {
  let count = 0; // 私有变量,外部无法直接访问

  return {
    increment: function () {
      count++;
      return count;
    },
    decrement: function () {
      count--;
      return count;
    },
    getCount: function () {
      return count;
    },
  };
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.increment()); // 2
console.log(counter.count); // undefined ✅ 无法直接访问

价值:保护数据不被意外篡改,提供清晰的 API 接口。

✅ 作用二:函数柯里化与参数复用

闭包可以"记住"之前传入的参数,从而实现函数的部分应用(Partial Application)。

场景:固定税率计算。

javascript 复制代码
function makeTaxCalculator(taxRate) {
  // taxRate 被闭包"记住"了
  return function (price) {
    return price * taxRate;
  };
}

const calcVAT = makeTaxCalculator(0.13); // 增值税率 13%
const calcIncomeTax = makeTaxCalculator(0.2); // 所得税率 20%

console.log(calcVAT(100)); // 13
console.log(calcIncomeTax(100)); // 20

价值:减少重复传参,提高代码复用性。

✅ 作用三:异步回调中的状态保持

在异步编程(如定时器、AJAX、事件监听)中,闭包确保回调函数执行时,依然能访问到定义时的上下文变量。

场景:延迟打印日志。

javascript 复制代码
function logAfterDelay(msg, delay) {
  setTimeout(function () {
    // 这里的 msg 和 delay 即使在 setTimeout 触发时(几秒后)依然可用
    console.log(`${msg} after ${delay}ms`);
  }, delay);
}

logAfterDelay("Hello", 1000);

3. 💻 代码实战:经典面试题解析

❌ 经典陷阱:循环中的闭包

这是面试中最常考的闭包问题。

javascript 复制代码
for (var i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, i * 1000);
}

预期输出 :1, 2, 3, 4, 5
实际输出:6, 6, 6, 6, 6 😱

原因

  • var 没有块级作用域,所有定时器共享同一个全局变量 i
  • 当定时器执行时,循环已经结束,i变成了 6。

✅ 解决方案 1:使用 IIFE(立即执行函数)创建闭包

javascript 复制代码
for (var i = 1; i <= 5; i++) {
  (function (j) {
    setTimeout(function timer() {
      console.log(j); // j 是每次循环独立的副本
    }, j * 1000);
  })(i);
}

原理 :IIFE 为每次循环创建了一个新的作用域,将当前的 i 值作为参数 j 传入并保存。

✅ 解决方案 2:使用 let(推荐)

javascript 复制代码
for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i);
  }, i * 1000);
}

原理let 具有块级作用域,JS 引擎会为每次循环迭代创建一个新的绑定。


4. ⚠️ 双刃剑:内存泄漏与性能

闭包虽然强大,但滥用会导致内存泄漏

📉 为什么会导致内存泄漏?

正常情况下,函数执行完后,局部变量会被回收。

但如果形成了闭包,且外部一直持有对该闭包函数的引用,那么闭包所引用的所有外部变量都不会被回收

危险示例

javascript 复制代码
function hugeDataHandler() {
  const hugeArray = new Array(1000000).fill("data"); // 占用大量内存

  return function () {
    console.log(hugeArray.length); // 只要这个函数存在,hugeArray 就不会被回收
  };
}

const handler = hugeDataHandler();
// 如果你不再需要 handler,必须手动解除引用
handler = null; // ✅ 允许 GC 回收 hugeArray

🛡️ 最佳实践

  1. 及时解除引用 :如果不再需要闭包函数,将其赋值为 null
  2. 避免在闭包中引用巨大的无用对象
  3. 谨慎使用全局闭包:尽量缩小闭包的作用范围。

💡 总结

特性 说明
本质 函数 + 引用的自由变量
核心能力 延长变量生命周期,实现数据私有化
主要用途 1. 封装私有变量 2. 柯里化/参数复用 3. 异步回调状态保持
副作用 可能导致内存泄漏,增加内存占用
解决循环陷阱 使用 let 或 IIFE

🚀 博主寄语

闭包不是魔法,它是 JS 作用域机制的自然产物。
不要为了用闭包而用闭包

当你需要隐藏数据记住状态复用逻辑时,闭包就是你的最佳伙伴。

记住口诀

函数嵌套生闭包,

外部变量怀里抱。

私有数据能封装,

异步回调少不了。

用完记得清引用,

内存泄漏要防好。

希望这篇文档能帮你彻底搞懂闭包的作用!如果有疑问,欢迎在评论区留言。👇

喜欢这篇文章吗?记得点赞、收藏、转发哦! ❤️

相关推荐
阿里嘎多学长1 小时前
2026-05-08 GitHub 热点项目精选
开发语言·程序员·github·代码托管
threelab1 小时前
挑战AI辅助从零构建3D模型编辑器:01基于Vue3 + Three.js的现代化架构设计
javascript·人工智能·3d·前端框架·着色器
张元清1 小时前
React 浏览器标签页 UX:用标题、Favicon 和通知把用户拉回来
前端·javascript·面试
知识分享小能手1 小时前
R语言入门学习教程,从入门到精通,集成开发环境RStudio(2)
开发语言·学习·r语言
葛兰岱尔1 小时前
葛兰岱尔rapid3D Loader for Three.js使用方式及7个基础API说明
开发语言·javascript·3d
Lkstar1 小时前
读完红宝书和YDKJS,我终于搞懂了原型链、闭包和this
javascript·面试
用户11489669441051 小时前
JavaScript原型链解析
javascript
毋语天1 小时前
Python 进阶:元组、字典、集合与函数全解析
开发语言·python
学习中.........1 小时前
操作系统底层原理、Java API 封装、以及高性能软件架构模式
java·开发语言