闭包
闭包 是指一个函数可以访问并操作其外部作用域中的变量。简单来说,当一个函数嵌套在另一个函数内部时,内部函数可以访问外部函数的变量,这就是闭包。
闭包的原理可以归结为两点:
- 函数可以作为参数或返回值传递;
- 函数可以访问其外部作用域的变量;
常见示例
模块化
通过闭包,可以创建私有变量和方法,从而实现模块化。这有助于保护内部实现细节,避免全局变量污染。
js
function createCounter() {
let count = 0;
function increment() {
count++;
}
function getCount() {
return count;
}
return { increment, getCount };
}
const { increment, getCount } = createCounter();
increment();
console.log(getCount()); // 1
事件处理
闭包可以用于在事件处理程序中保存状态信息。
js
function createButton(text) {
let clickCount = 0;
const button = document.createElement("button");
button.innerText = text;
button.addEventListener("click", () => {
clickCount++;
console.log(`${text} button clicked ${clickCount} times.`);
});
return button;
}
const button = createButton("按钮");
document.body.appendChild(button);
函数柯里化
闭包可以用于实现函数柯里化,将多参数函数转换为一系列单参数函数。
js
function curry(fn) {
// fn.length获取当前函数的参数个数
const arity = fn.length;
function curried(...args) {
// 当参数大于 arity 时,执行该函数
if (args.length >= arity) {
return fn.apply(this, args);
} else {
// 参数少于 arity 时,会直接返回当前函数,以便链式调用
return function (...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
}
}
return curried;
}
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
延迟执行
闭包可以用来延迟执行函数,避免在短时间内频繁执行一个函数。
js
function debounce(fn, delay) {
let timeoutId;
return function () {
const that = this;
const args = arguments;
// 先清除上一次的定时器
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn.apply(that, args);
}, delay);
};
}
function doSomething() {
console.log("Doing something...");
}
const d = debounce(doSomething, 1000);
d(); // Doing something...
缓存数据
通过闭包来缓存数据,可以避免重复计算,提高效率。
js
function fibonacci() {
const cache = {};
function fib(n) {
if (n < 2) {
return 1;
}
// 缓存数据,提高效率
if (cache[n]) {
return cache[n];
}
cache[n] = fib(n - 1) + fib(n - 2);
return cache[n];
}
return fib;
}
const fib = fibonacci();
// 循环输出前 20 项
// 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
for (let i = 0; i <= 20; i++) {
console.log(fib(i));
}
js
function fibonacci(n) {
if (n < 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
for (let i = 0; i < 20; i++) {
console.log(fibonacci(i));
}
闭包缺点
虽然闭包很有用,但也需要注意一下两点:
- 内存泄漏:由于闭包会引用外部作用域的变量,这可能导致内存泄漏。当不再需要闭包时,应该确保解除对外部变量的引用,以便垃圾回收器回收内存;
- 性能:过度使用闭包可能导致性能下降,谨慎使用闭包;
如何避免闭包内存泄漏
闭包会引用外部函数中的变量和函数,如果这些变量和函数没有及时释放,就可能导致内存泄漏的问题。
手动清除
在闭包的最后,手动将外部变量和函数置为 null,以释放内存。
js
function outer() {
let a = 1;
return function inner() {
console.log(1);
a = null;
inner = null;
};
}
const out = outer();
out();
使用立即执行函数
使用立即执行函数创建一个独立的作用域,并在作用域结束时自动清除变量和函数。
js
function outer() {
let a = 1;
return (function inner() {
console.log(1);
})();
}
const out = outer();
避免循环引用
当闭包和外部对象之间存在循环引用时,需要特别注意变量的清除。可以在外部对象中定义一个方法来清除闭包变量。
js
function outer() {
let a = 1;
const obj = {
inner: () => {
console.log(a);
},
clear: () => {
a = null;
obj.inner = null;
}
};
return obj;
}
const out = outer();