闭包:概念、应用与内存管理
理解闭包原理,掌握防抖节流实现,避免内存泄漏
问题
什么是闭包?如何用闭包实现防抖、节流和模块化?闭包会导致什么内存问题?
解答
什么是闭包
闭包是指函数能够访问其词法作用域中的变量,即使函数在该作用域外执行。
function outer() {
let count = 0; // 外部函数的变量
return function inner() {
count++; // 内部函数访问外部变量
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
inner 函数持有对 count 的引用,即使 outer 已执行完毕,count 仍然存活。
应用场景
1. 防抖(Debounce)
连续触发时,只执行最后一次。
function debounce(fn, delay) {
let timer = null; // 闭包保存定时器 ID
return function (...args) {
// 清除上一次的定时器
clearTimeout(timer);
// 重新设置定时器
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用示例
const handleSearch = debounce((keyword) => {
console.log('搜索:', keyword);
}, 300);
// 模拟连续输入
handleSearch('a');
handleSearch('ab');
handleSearch('abc'); // 只有这次会执行
2. 节流(Throttle)
固定时间间隔内只执行一次。
function throttle(fn, interval) {
let lastTime = 0; // 闭包保存上次执行时间
return function (...args) {
const now = Date.now();
// 距离上次执行超过间隔时间才执行
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 使用示例
const handleScroll = throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 200);
window.addEventListener('scroll', handleScroll);
3. 模块化
用闭包实现私有变量和方法。
const userModule = (function () {
// 私有变量,外部无法直接访问
let users = [];
let idCounter = 1;
// 私有方法
function generateId() {
return idCounter++;
}
// 公开 API
return {
add(name) {
const user = { id: generateId(), name };
users.push(user);
return user;
},
remove(id) {
users = users.filter(u => u.id !== id);
},
getAll() {
return [...users]; // 返回副本,保护原数据
}
};
})();
// 使用
userModule.add('Alice');
userModule.add('Bob');
console.log(userModule.getAll()); // [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
console.log(userModule.users); // undefined,无法访问私有变量
内存泄漏问题
闭包会阻止变量被垃圾回收,使用不当会导致内存泄漏。
// 问题示例:DOM 引用导致泄漏
function bindEvent() {
const element = document.getElementById('button');
const largeData = new Array(1000000).fill('x'); // 大数据
element.addEventListener('click', function () {
// 闭包引用了 largeData,即使不使用也不会被回收
console.log('clicked');
});
}
// 解决方案 1:及时清除引用
function bindEventFixed() {
const element = document.getElementById('button');
let largeData = new Array(1000000).fill('x');
// 用完就清除
processData(largeData);
largeData = null; // 解除引用
element.addEventListener('click', function () {
console.log('clicked');
});
}
// 解决方案 2:移除事件监听
function bindEventWithCleanup() {
const element = document.getElementById('button');
function handleClick() {
console.log('clicked');
// 执行后移除监听
element.removeEventListener('click', handleClick);
}
element.addEventListener('click', handleClick);
}
关键点
- 闭包 = 函数 + 其词法环境的引用
- 防抖:延迟执行,重复触发会重置计时器
- 节流:固定频率执行,忽略间隔内的重复调用
- 模块化:利用闭包实现私有变量,只暴露公开 API
- 内存管理:不再使用的闭包变量要手动置为
null,及时移除事件监听
目录