闭包
闭包的概念
在JavaScript中,闭包(Closure)是一个非常核心且强大的概念。简单来说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
在JavaScript中,每当创建一个函数,闭包就会在函数创建时刻被生成。
闭包的核心定义
闭包 = 函数 + 该函数能访问的自由变量(环境)
通常情况下,函数内部的局部变量在函数执行完毕后就会被销毁(垃圾回收)。但如果这个函数内部又定义了一个子函数,并且子函数引用了父函数的变量,那么即使父函数执行结束,这些变量依然会“存活”在内存中,供子函数使用。
一个经典的例子
function createCounter() {
let count = 0; // 这个变量被“闭包”保护起来了
return function() {
count++; // 引用了外部作用域的变量
console.log(count);
};
}
const counter = createCounter();
counter(); // 输出: 1
counter(); // 输出: 2
counter(); // 输出: 3
为什么count没有被销毁?当createCounter执行完毕时,按理说count应该消失。但因为返回的匿名函数仍然持有对count的引用JavaScript的垃圾回收机制(GC)发现这个变量还有人使用,所以将其保留在了内存中。
闭包的实际用途
-
私有变量(封装)
在没有ES6
class私有属性之前,闭包是实现封装的唯一方法。function user(name) { return { getName: function() { return name; // 外部无法直接修改 name,只能通过接口获取 } }; } -
柯里化(Currying)与函数工厂
柯里化:
// 普通函数:每次都要传三个参数 function log(date, type, message) { console.log(`[${date}] [${type}] ${message}`); } // 柯里化版本 function curryLog(date) { return function(type) { return function(message) { console.log(`[${date}] [${type}] ${message}`); }; }; } // 使用方式: const todayLog = curryLog("2025-12-31"); // 固定日期 const errorLog = todayLog("ERROR"); // 固定类型为错误 errorLog("内存泄漏:闭包未释放"); // 输出: [2025-12-31] [ERROR] 内存泄漏:闭包未释放可以根据参数创建特定的函数:
function makeAdder(x) { return function(y) { return x + y; }; } const add5 = makeAdder(5); console.log(add5(10)); // 15
闭包的“副作用”
虽然闭包很好用,但如果使用不当,会带来一些问题:
-
内存泄漏
由于闭包会保持对外部变量的引用,如果闭包对象长期不被销毁,这些变量占用的内存就无法释放。
-
性能开销
闭包涉及作用域链的查找,相比普通函数,内存消耗和执行速度会有细微的影响。