闭包(Closure)是JavaScript中最强大也最容易让人困惑的特性之一。简单来说:
闭包是指有权访问另一个函数作用域中变量的函数,即使外部函数已经执行完毕。
假设我们需要实现一个计数器:
// 普通实现(存在问题)
let count = 0;
function increment() {
count++;
return count;
}
console.log(increment()); // 1
console.log(increment()); // 2
问题:count
是全局变量,容易被意外修改。
闭包解决方案:
function createCounter() {
let count = 0; // 局部变量
return function() {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
此时count
被完美保护在闭包中,外部无法直接访问。
JavaScript的作用域规则:
function outer() {
const x = 10;
function inner() {
console.log(x); // 访问outer的x
}
return inner;
}
const innerFn = outer();
innerFn(); // 输出10
关键点:inner
函数记住了它被创建时的作用域链,即使outer
已经执行完毕。
function outer() {
const largeData = new Array(1000000).fill("data"); // 大数据
return function() {
console.log("Closure is alive!");
};
}
const closure = outer();
内存问题:largeData
不会被释放,因为闭包保持着对它的引用!
function createPerson(name) {
let _age = 0; // "私有"变量
return {
getName: () => name,
getAge: () => _age,
setAge: (age) => { _age = age; }
};
}
const person = createPerson("Alice");
console.log(person.getName()); // "Alice"
person.setAge(25);
console.log(person.getAge()); // 25
console.log(person._age); // undefined(无法直接访问)
function powerFactory(exponent) {
return function(base) {
return Math.pow(base, exponent);
};
}
const square = powerFactory(2);
const cube = powerFactory(3);
console.log(square(5)); // 25
console.log(cube(5)); // 125
function setupButtons() {
const buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
(function(index) {
buttons[index].onclick = function() {
console.log("Button " + index + " clicked");
};
})(i);
}
}
问题解决:使用IIFE(立即执行函数)为每个按钮回调创建独立的作用域。
const calculator = (function() {
let _memory = 0; // 私有变量
return {
add: (x) => { _memory += x; },
subtract: (x) => { _memory -= x; },
getResult: () => _memory,
clear: () => { _memory = 0; }
};
})();
calculator.add(10);
calculator.subtract(5);
console.log(calculator.getResult()); // 5
console.log(calculator._memory); // undefined
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
console.log("From cache");
return cache[key];
}
const result = fn(...args);
cache[key] = result;
return result;
};
}
const factorial = memoize(function(n) {
return n <= 1 ? 1 : n * factorial(n - 1);
});
console.log(factorial(5)); // 计算
console.log(factorial(5)); // 从缓存读取
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
const sum = (a, b, c) => a + b + c;
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
典型问题:
function createHeavyObject() {
const bigData = new Array(1000000).fill("data");
return function() {
console.log("Leaking...");
};
}
const leakyFn = createHeavyObject();
// bigData不会被释放!
解决方案:
leakyFn = null; // 手动解除引用
经典问题:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 全部输出5!
}, 100);
}
解决方案:
// 方案1:使用let(块级作用域)
for (let i = 0; i < 5; i++) {
setTimeout(() => console.log(i), 100);
}
// 方案2:IIFE
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
JavaScript引擎通过词法环境实现闭包:
function outer() {
const x = 10;
function inner() {
console.log(x);
}
return inner;
}
inner
的词法环境:
innerLexicalEnvironment = {
EnvironmentRecord: {},
Outer: outerLexicalEnvironment // 包含x=10
}
现代JavaScript引擎(如V8)会对闭包进行优化:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1);
}
答案:3, 3, 3
(因为var
没有块级作用域)
function Car() {
let speed = 0;
function accelerate() {
speed += 10;
}
return {
getSpeed: () => speed,
pressPedal: () => accelerate()
};
}
const myCar = Car();
myCar.pressPedal();
console.log(myCar.getSpeed()); // 10
console.log(myCar.speed); // undefined
闭包的核心要点:
最佳实践:
let
而非闭包进一步学习:
你在使用闭包时遇到过哪些有趣的问题?欢迎在评论区分享!