闭包
闭包是JavaScript中的一个重要概念,它允许函数访问并操作其词法作用域之外的变量。
基本概念
闭包是指有权访问另一个函数作用域中变量的函数:
javascript
function outerFunction() {
const outerVariable = '外部变量';
function innerFunction() {
console.log(outerVariable); // 可以访问外部变量
}
return innerFunction;
}
const inner = outerFunction();
inner(); // 输出: 外部变量
在这个例子中,innerFunction是一个闭包,它可以访问outerFunction作用域中的outerVariable变量,即使outerFunction已经执行完毕。
闭包的工作原理
词法作用域
JavaScript使用词法作用域,也就是说,函数的作用域在函数定义时就确定了,而不是在函数调用时。
作用域链
当访问一个变量时,JavaScript会首先在当前函数作用域中查找,如果找不到,就会向上查找外层函数的作用域,直到找到该变量或到达全局作用域。
闭包的形成
当一个函数返回另一个函数时,返回的函数会保留对其词法作用域的引用,这就形成了闭包。
javascript
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter1 = makeCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
const counter2 = makeCounter();
console.log(counter2()); // 1(独立的闭包)
闭包的应用场景
1. 数据封装和私有变量
闭包可以用来创建私有变量,实现数据封装:
javascript
function createPerson(name, age) {
return {
getName: function() {
return name;
},
getAge: function() {
return age;
},
setAge: function(newAge) {
if (newAge > 0 && newAge < 150) {
age = newAge;
}
}
};
}
const person = createPerson('John', 30);
console.log(person.getName()); // John
console.log(person.getAge()); // 30
person.setAge(35);
console.log(person.getAge()); // 35
// 无法直接访问name和age
console.log(person.name); // undefined
2. 函数工厂
使用闭包创建具有相似功能的函数:
javascript
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. 事件处理
在事件处理中使用闭包保存状态:
javascript
for (var i = 0; i < 5; i++) {
(function(index) {
const button = document.createElement('button');
button.textContent = '按钮 ' + index;
button.addEventListener('click', function() {
console.log('点击了按钮 ' + index);
});
document.body.appendChild(button);
})(i); // 立即执行函数,创建闭包
}
4. 模块模式
使用闭包实现模块化,避免全局命名冲突:
javascript
const counterModule = (function() {
let count = 0;
function increment() {
count++;
}
function decrement() {
count--;
}
function getCount() {
return count;
}
return {
increment,
decrement,
getCount
};
})();
counterModule.increment();
counterModule.increment();
console.log(counterModule.getCount()); // 2
5. 记忆化
使用闭包缓存函数结果,提高性能:
javascript
function memoize(func) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key]) {
return cache[key];
}
const result = func.apply(this, args);
cache[key] = result;
return result;
};
}
const slowFunction = function(n) {
console.log('计算...');
let result = 0;
for (let i = 0; i < n; i++) {
result += i;
}
return result;
};
const fastFunction = memoize(slowFunction);
console.log(fastFunction(1000)); // 计算... 499500
console.log(fastFunction(1000)); // 499500(从缓存获取)
闭包的注意事项
内存泄漏
闭包会保留对外部变量的引用,如果闭包被长期保存,可能会导致内存泄漏:
javascript
function createElement() {
const element = document.createElement('div');
// 闭包引用element,即使DOM中删除了element,也不会被垃圾回收
element.addEventListener('click', function() {
console.log('元素被点击');
});
return element;
}
const div = createElement();
document.body.appendChild(div);
// ... 之后删除元素
document.body.removeChild(div);
// 但闭包仍然引用div,可能导致内存泄漏
解决方法:
javascript
function createElement() {
const element = document.createElement('div');
function handleClick() {
console.log('元素被点击');
}
element.addEventListener('click', handleClick);
// 提供清理函数
element.removeEventListener = function() {
element.removeEventListener('click', handleClick);
};
return element;
}
循环中的闭包问题
在使用var声明变量的循环中,闭包可能会导致意外行为:
javascript
// 问题代码
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出5次5
}, 1000);
}
解决方法:
- 使用let声明变量(ES6+):
javascript
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出0, 1, 2, 3, 4
}, 1000);
}
- 使用立即执行函数:
javascript
for (var i = 0; i < 5; i++) {
(function(index) {
setTimeout(function() {
console.log(index); // 输出0, 1, 2, 3, 4
}, 1000);
})(i);
}
闭包与this
在闭包中使用this时需要注意,this的值取决于函数的调用方式:
javascript
const obj = {
name: 'John',
sayName: function() {
return function() {
console.log(this.name); // this指向全局对象
};
}
};
obj.sayName()(); // 输出: undefined(在严格模式下)
解决方法:
- 使用箭头函数(继承外部作用域的this):
javascript
const obj = {
name: 'John',
sayName: function() {
return () => {
console.log(this.name); // this指向obj
};
}
};
obj.sayName()(); // 输出: John
- 保存this到变量:
javascript
const obj = {
name: 'John',
sayName: function() {
const self = this;
return function() {
console.log(self.name); // 使用保存的self
};
}
};
obj.sayName()(); // 输出: John
学习资源
继续学习:作用域与提升
最后更新:2026-02-08