JavaScript闭包详解
JavaScript闭包详解
什么是闭包?
闭包(Closure)是JavaScript中的一个重要概念。简单来说,闭包是指有权访问另一个函数作用域中变量的函数。更具体地说,闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量。
闭包的形成
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行的。
function createCounter() {
let count = 0; // 私有变量
return function() {
count++; // 访问外部函数的变量
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
在上面的例子中,counter
是一个闭包,它"记住了"它被创建时的环境(包含count
变量)。
闭包的特性
-
访问外部函数作用域:闭包可以访问外部函数的变量,即使外部函数已经返回。
-
保持状态:闭包可以"记住"它被创建时的环境。
-
封装私有变量:闭包可以用来创建私有变量,外部无法直接访问。
闭包的实际应用
1. 数据私有化
function createPerson(name) {
let age = 0;
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
setAge: function(newAge) {
if (newAge > 0) {
age = newAge;
}
}
};
}
const person = createPerson('张三');
console.log(person.getName()); // 张三
person.setAge(25);
console.log(person.getAge()); // 25
console.log(person.age); // undefined,无法直接访问
2. 函数工厂
function makeAdder(x) {
return function(y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
3. 模块模式
const calculator = (function() {
let result = 0;
return {
add: function(x) {
result += x;
return this;
},
subtract: function(x) {
result -= x;
return this;
},
multiply: function(x) {
result *= x;
return this;
},
getResult: function() {
return result;
}
};
})();
calculator.add(5).multiply(2).subtract(3);
console.log(calculator.getResult()); // 7
闭包的注意事项
1. 内存泄漏
闭包会保持对外部变量的引用,如果不小心,可能导致内存泄漏。
function createLargeArray() {
let largeArray = new Array(1000000).fill('大数组');
return function() {
console.log(largeArray[0]); // 持有对大数组的引用
};
}
const printArray = createLargeArray(); // largeArray 无法被垃圾回收
// ...使用后
printArray = null; // 手动释放引用
2. 循环中创建闭包的问题
// 常见错误
function createFunctions() {
var result = [];
for (var i = 0; i < 3; i++) {
result.push(function() {
console.log(i); // 所有函数都会打印 3
});
}
return result;
}
// 正确做法
function createFunctions() {
var result = [];
for (var i = 0; i < 3; i++) {
result.push(
(function(num) {
return function() {
console.log(num); // 0, 1, 2
};
})(i)
);
}
return result;
}
// 使用 let (ES6)
function createFunctions() {
var result = [];
for (let i = 0; i < 3; i++) {
result.push(function() {
console.log(i); // 0, 1, 2 (let 创建块级作用域)
});
}
return result;
}
常见面试题
题目1:下面的代码输出什么?
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
答案:连续输出五次 6。
原因:setTimeout 中的函数形成了闭包,但是它们都引用同一个变量 i。当 setTimeout 中的函数执行时,循环已经结束,i 的值已经变成了 6。
修复方案:
// 使用IIFE
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 1000);
})(i);
}
// 使用let
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
题目2:实现一个缓存函数
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (!cache[key]) {
cache[key] = fn.apply(this, args);
}
return cache[key];
};
}
// 使用
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFib = memoize(fibonacci);
console.time('未缓存');
console.log(fibonacci(35));
console.timeEnd('未缓存');
console.time('已缓存');
console.log(memoizedFib(35));
console.timeEnd('已缓存');
总结
闭包是JavaScript中非常强大的特性,理解闭包对于深入学习JavaScript非常重要。闭包主要用于数据隐藏和封装,创建私有变量,以及在特定场景下保持状态。但使用不当也可能导致内存泄漏等问题,需要谨慎使用。在面试中,闭包相关的问题是检验候选人JavaScript基础是否扎实的常见题目。