闭包的理解
1. 闭包的基本概念
闭包是指那些能够访问自由变量的函数。这里的自由变量是指在函数中使用的,但既不是函数参数也不是函数局部变量的变量。
更通俗地说:闭包是能够读取其他函数内部变量的函数,或者说 闭包是函数和声明该函数的词法环境的组合。
2. 闭包的形成条件
一个闭包的形成通常需要满足以下条件:
存在函数嵌套
内部函数引用了外部函数的变量
内部函数在外部函数之外被调用
3. 闭包的简单示例
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const closure = outer();
closure(); // 输出 1
closure(); // 输出 2
closure(); // 输出 34. 闭包的工作原理
闭包之所以能访问外部函数的变量,是因为JavaScript的作用域链机制:
每个函数在创建时都会记住自己所在的词法环境(Lexical Environment)
当函数执行时,如果在当前环境中找不到变量,就会向上一级作用域查找
即使外部函数已经执行完毕,其作用域链仍然被内部函数引用着,因此不会被垃圾回收
5. 闭包的主要用途
5.1 创建私有变量: 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.decrement()); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined (无法直接访问)5.2 数据封装和模块模式
const module = (function() {
let privateVar = '我是私有的';
function privateMethod() {
console.log(privateVar);
}
return {
publicMethod: function() {
privateMethod();
},
publicVar: '我是公开的'
};
})();
module.publicMethod(); // "我是私有的"
console.log(module.publicVar); // "我是公开的"
console.log(module.privateVar); // undefined
module.privateMethod(); // Error5.3 函数工厂
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 155.4 在循环中保持变量
// 有问题的方式
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出5个5
}, 100);
}
// 使用闭包解决
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出0,1,2,3,4
}, 100);
})(i);
}
// 或者使用let(ES6)
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出0,1,2,3,4
}, 100);
}6. 闭包的优缺点
优点:
可以创建私有变量和方法,实现封装
可以让变量长期驻留在内存中
可以实现模块化编程
可以避免全局变量的污染
缺点:
由于闭包会保留对外部变量的引用,可能导致内存泄漏
过度使用闭包可能导致内存消耗过大
闭包中的this指向需要注意(闭包中的this通常指向全局对象或undefined,而不是创建它的函数)
7. 闭包与内存管理
闭包可能导致的内存泄漏问题:
function leakMemory() {
let largeArray = new Array(1000000).fill('*');
return function() {
console.log('闭包保留了largeArray的引用');
};
}
const leaky = leakMemory();
// 即使不再需要largeArray,它也不会被垃圾回收,因为闭包保留了引用解决方法是在不需要时手动解除引用:
leaky = null; // 解除引用,允许垃圾回收8. 闭包的高级应用
8.1 柯里化(Currying)
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));
};
}
};
}
function sum(a, b, c) {
return a + b + c;
}
const curriedSum = curry(sum);
console.log(curriedSum(1)(2)(3)); // 6
console.log(curriedSum(1, 2)(3)); // 6
console.log(curriedSum(1, 2, 3, 4)); // 6 因为sum方法8.2 函数记忆(Memoization)
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (key in cache) {
return cache[key];
}
const result = fn.apply(this, args);
cache[key] = result;
return result;
};
}
function expensiveCalculation(n) {
console.log('计算中...');
return n * n;
}
const memoized = memoize(expensiveCalculation);
console.log(memoized(5)); // 计算中... 25
console.log(memoized(5)); // 25 (直接从缓存读取)9. 常见面试题解析
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出什么?为什么?如何修改?
答案:输出3个3。因为setTimeout是异步的,当回调执行时循环已经结束,i的值已经是3。可以使用闭包或let解决。function createFunctions() {
var result = [];
for (var i = 0; i < 3; i++) {
result[i] = function() {
return i;
};
}
return result;
}
var funcs = createFunctions();
console.log(funcs[0]()); // ?
console.log(funcs[1]()); // ?
console.log(funcs[2]()); // ?
// 答案:都输出3。因为所有函数共享同一个i变量。解决方法是用闭包或let。10. 总结
闭包是JavaScript中一个强大且灵活的特性,它允许函数访问并记住其词法作用域,即使函数在其词法作用域之外执行。理解闭包对于编写高效、模块化和可维护的JavaScript代码至关重要。
正确使用闭包可以实现:
数据私有化
函数工厂
模块模式
柯里化
记忆化等高级功能
但同时也要注意闭包可能带来的内存问题,合理使用才能发挥其最大价值。