
干货分享让你分分钟学会 javascript 闭包什么是闭包先从一个生活场景说起想象一下你有一个秘密日记本只有你自己能打开它。但是你可以在日记本里放一个“魔法信封”这个信封允许你给朋友写一封信信封里装着日记本的部分内容。你的朋友拿到信封后每次打开信封都能看到同样的内容即使你后来修改了日记本信封里的内容依然不变。在 JavaScript 中闭包就是这个“魔法信封”。它可以让一个函数记住并访问它被创建时的作用域中的变量即使这个函数在更晚的时间、在其他地方被调用。简单来说闭包就是函数 它能够访问的外部变量。## 闭包的核心作用域链要理解闭包必须先理解 JavaScript 的作用域机制。每个函数在创建时都会形成一个作用域链。这个链记录了函数自身的作用域和所有父级作用域。当函数执行时它会沿着作用域链逐层查找变量。javascript// 示例1闭包的基本结构function createCounter() { let count 0; // 外部变量 // 内部函数这是一个闭包 return function() { count; // 访问外部变量 console.log(当前计数:, count); };}const counter createCounter(); // 创建闭包counter(); // 输出: 当前计数: 1counter(); // 输出: 当前计数: 2counter(); // 输出: 当前计数: 3在这个例子中createCounter函数返回了一个匿名函数。这个匿名函数虽然被返回到了外部但它仍然“记住”了count变量。每次调用counter()时都会修改同一个count变量因为闭包保持了对外部变量的引用。## 闭包的三大特性### 1. 持久化变量闭包可以让变量“活”在函数执行完成后。通常局部变量在函数执行完毕后会被销毁但闭包会保持对外部变量的引用使它们不被垃圾回收。### 2. 私有性闭包可以模拟私有变量因为外部无法直接访问闭包内部的变量只能通过闭包提供的接口来操作。### 3. 延迟执行闭包可以让函数在未来的某个时刻执行同时保留当时的上下文。## 实战用闭包实现一个缓存函数下面是一个更实用的例子展示如何使用闭包来创建带有缓存的函数这在性能优化中非常常见。javascript// 示例2使用闭包实现缓存计算function createCachedCalculator() { const cache {}; // 私有缓存对象 // 返回一个闭包用于计算平方但带缓存 return function(number) { // 检查缓存中是否已有结果 if (cache[number] ! undefined) { console.log(从缓存中获取:, number); return cache[number]; } // 模拟耗时的计算 console.log(正在计算:, number); const result number * number; cache[number] result; // 存入缓存 return result; };}const cachedSquare createCachedCalculator();console.log(cachedSquare(5)); // 输出: 正在计算: 5 \n 25console.log(cachedSquare(5)); // 输出: 从缓存中获取: 5 \n 25console.log(cachedSquare(3)); // 输出: 正在计算: 3 \n 9console.log(cachedSquare(3)); // 输出: 从缓存中获取: 3 \n 9这个例子中cache变量是私有且持久的只有cachedSquare函数才能访问和修改它。每次调用时都会先检查缓存避免重复计算。## 闭包的常见陷阱循环中的闭包初学者常犯的错误是在循环中使用闭包时误以为每个闭包会捕获不同的变量值。实际上所有闭包共享同一个变量。javascript// 经典陷阱循环中的闭包function createButtons() { for (var i 0; i 5; i) { setTimeout(function() { console.log(按钮, i); // 期望输出 0,1,2,3,4 }, 100); }}createButtons(); // 实际输出: 按钮 5 (打印5次)解决方案是使用let代替varlet会为每次循环创建新的块级作用域或者使用立即执行函数表达式IIFE来创建独立作用域。javascript// 正确做法使用 letfunction createButtonsFixed() { for (let i 0; i 5; i) { setTimeout(function() { console.log(按钮, i); // 输出 0,1,2,3,4 }, 100); }}createButtonsFixed();## 闭包的实际应用场景1.模块模式创建私有变量和方法暴露公共接口2.节流和防抖在事件处理中保留计时器状态3.柯里化将多参数函数转换为单参数函数链4.回调函数在异步操作中保留上下文5.函数工厂根据参数生成不同行为的函数## 闭包与内存管理闭包虽然强大但也需要注意内存泄漏问题。因为闭包会持有外部变量的引用如果闭包长期存在如全局变量它引用的变量就无法被垃圾回收。在大型应用中如果创建大量闭包且不释放可能导致内存占用过高。最佳实践- 在不需要时将闭包设为null以便垃圾回收- 避免在全局作用域中创建不必要的闭包- 使用完闭包后及时解除引用## 总结闭包是 JavaScript 中最核心、最迷人的概念之一。它本质上是函数 该函数被创建时的作用域的组合。通过闭包我们可以实现变量的持久化、数据隐藏和模块化设计。理解闭包的关键在于- 每个函数都有自己的作用域链- 内部函数可以访问外部函数的变量- 即使外部函数执行完毕内部函数仍然保持这些变量的引用掌握闭包后你会发现许多高级编程模式如函数式编程、模块模式都变得清晰起来。建议你在实际项目中多尝试使用闭包从简单的计数器开始逐步应用到缓存、节流等场景中。记住闭包不是魔法它是 JavaScript 作用域机制的必然结果。当你真正理解作用域链时闭包就自然变得通透了。