什么是闭包?
在了解闭包之前,我们首先需要搞懂什么是闭包,闭包指的是一个有权访问另一个函数作用域中变量的函数。
为什么需要闭包?
我们知道在JS中,变量分为全局变量和局部变量,全局变量的作用域为全局作用域,局部变量的作用域为局部作用域,我们可以在函数内部访问全局变量,但是无法在全局环境中访问函数的局部变量,闭包可以帮助我们访问到这个局部变量。
闭包的应用场景
1. 数据私有化 / 模块化模式
这是最经典的应用,用来创建私有变量:
function createCounter() {
let count = 0; // 私有变量,外部无法直接访问
return {
increment() {
count++;
return count;
},
decrement() {
count--;
return count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
console.log(counter.count); // undefined - 无法直接访问
console.log(counter.getCount()); // 2 - 只能通过方法访问
实际应用:比如购物车功能、状态管理器等,需要保护内部数据不被外部随意修改。
2. 防抖(Debounce)和节流(Throttle)
这是性能优化中非常常用的场景:
// 防抖:搜索框输入
function debounce(fn, delay) {
let timer = null; // 闭包保存 timer
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用场景
const searchInput = document.getElementById('search');
const handleSearch = debounce(function(e) {
console.log('发起搜索请求:', e.target.value);
}, 500);
searchInput.addEventListener('input', handleSearch);
// 节流:滚动事件
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
// 使用场景
window.addEventListener('scroll', throttle(function() {
console.log('处理滚动事件');
}, 200));
实际应用:搜索框输入联想、页面滚动加载、窗口 resize 事件等。
3. 函数柯里化(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 log(level, time, message) {
console.log(`[${level}] ${time}: ${message}`);
}
const curriedLog = curry(log);
const errorLog = curriedLog('ERROR');
const errorLogNow = errorLog(new Date().toISOString());
errorLogNow('数据库连接失败'); // [ERROR] 2025-11-18T...: 数据库连接失败
errorLogNow('API 请求超时'); // [ERROR] 2025-11-18T...: API 请求超时
实际应用:参数复用、函数式编程、配置预设等。
4. React Hooks(useState, useEffect 等)
React 的 Hooks 底层就是基于闭包实现的:
// 简化版 useState 实现原理
function useState(initialValue) {
let state = initialValue; // 闭包保存状态
function setState(newValue) {
state = newValue;
render(); // 触发重新渲染
}
return [state, setState];
}
// 实际使用
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
实际应用:React 组件状态管理、副作用处理等。
5. 延迟执行 / 偏函数应用
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn(...presetArgs, ...laterArgs);
};
}
// 实际应用:API 请求封装
function request(method, url, data) {
return fetch(url, { method, body: JSON.stringify(data) });
}
const get = partial(request, 'GET');
const post = partial(request, 'POST');
get('/api/users');
post('/api/users', { name: 'John' });
6. 定时器管理
function createTimer() {
let timerId = null;
return {
start(callback, delay) {
this.stop();
timerId = setInterval(callback, delay);
},
stop() {
if (timerId) {
clearInterval(timerId);
timerId = null;
}
}
};
}
const timer = createTimer();
timer.start(() => console.log('tick'), 1000);
// 稍后
timer.stop();
总结
闭包的核心价值在于:
- 数据持久化:在函数执行完后仍然保留变量
- 数据私有化:创建私有作用域,保护数据
- 参数预设:创建特定配置的函数
在实际开发中,几乎每天都在使用闭包,只是有时候我们没有意识到。像事件处理、定时器、回调函数、Promise、async/await 等,背后都涉及到闭包的概念。
闭包解决了什么问题?(闭包的作用)
- 闭包可以缓存上级作用域,使得函数外部打破了函数作用域的束缚,可以访问函数内部的变量。
- 让变量的值始终保持在内存中。
闭包带来了什么问题?(闭包的缺陷)
闭包会导致函数的变量一直保存在内存中,过多的闭包会导致内存泄露。
数组的哪些方法用到了闭包?
- 例如forEach
const arr = [1,2,3];
arr.forEach((item,index) => {
setTimeout(() => {
console.log(item);
},1000)
})
只要是符合闭包的定义,一个有权访问另一个函数作用域中变量的函数就是闭包。权威指南中说,严格意义来说JS中的函数都是闭包。