闭包的定义(Closure)
闭包是一个函数,可以访问其外部作用域(即外部函数的变量)。闭包由一个函数和其周围的状态(词法环境)的构成。闭包常用于数据私有化和创建工厂函数等。
举个例子🌰
javascript
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const increment = outer();
console.log(increment()); // 输出:1
console.log(increment()); // 输出:2
闭包的用途
- 数据封装 :闭包可以用来创建私有变量,封装数据,防止外部直接访问。
- 函数工厂:可以创建并返回具有特定行为的函数。
- 回调函数:在异步编程中,闭包可以用来捕获和使用外部变量。
举例
数据封装和模块化
闭包可以用来创建私有变量和方法,从而实现模块化。这是JavaScript中实现封装的一种常用方式。
javascript
const myModule = (function() {
let privateVariable = 'secret';
function privateFunction() {
console.log('这是一个私有函数');
}
return {
publicMethod: function() {
console.log('私有变量:' + privateVariable);
privateFunction();
}
};
})();
myModule.publicMethod(); // 输出:这是一个私有函数
在这个示例中,privateVariable
和 privateFunction
是私有变量和方法,只能在自执行函数的作用域内访问。publicMethod
是一个公共方法,可以通过 myModule
对象调用,它可以访问私有变量和方法。
函数工厂
闭包可以用来创建并返回具有特定行为的函数。这些函数可以携带和使用外部作用域中的变量。
示例:创建计数器
javascript
function createCounter(initialValue = 0) {
let count = initialValue;
return {
increment: function() {
count += 1;
console.log(count);
},
decrement: function() {
count -= 1;
console.log(count);
},
getValue: function() {
return count;
}
};
}
const counter1 = createCounter(5);
counter1.increment(); // 输出:6
counter1.increment(); // 输出:7
counter1.decrement(); // 输出:6
const counter2 = createCounter(10);
counter2.increment(); // 输出:11
事件处理
闭包可以用来在事件处理函数中捕获和使用外部变量。
示例:动态生成按钮并绑定事件
在这个示例中,createButton
函数创建一个按钮并绑定点击事件。每个按钮的点击事件处理函数可以访问外部变量 i
。
异步编程
闭包可以用来在异步回调中捕获和使用外部变量。
示例:定时器
javascript
function setupTimer(message, delay) {
setTimeout(function() {
console.log(message);
}, delay);
}
setupTimer('Hello, world!', 2000); // 2秒后输出:Hello, world!
函数柯里化
闭包可以用来实现函数柯里化。
js
function curry(fn, ...args) {
return function(...newArgs) {
return fn(...args, ...newArgs);
};
}
function add(a, b, c) {
return a + b + c;
}
const add5 = curry(add, 5);
console.log(add5(10, 15)); // 输出:30
const add5And10 = curry(add, 5, 10);
console.log(add5And10(15)); // 输出:30
装饰器模式
闭包可以用来创建装饰器,扩展或修改现有函数的行为。
示例:装饰器模式
js
function loggingDecorator(fn) {
return function(...args) {
console.log(`函数 ${fn.name}, 参数: ${args.join(', ')}`);
const result = fn(...args);
console.log(`结果: ${result}`);
return result;
};
}
function add(a, b) {
return a + b;
}
const loggedAdd = loggingDecorator(add);
console.log(loggedAdd(3, 4));
// 输出:函数 add, 参数: 3, 4
// 输出:结果: 7
// 输出:7
在这个示例中,loggingDecorator
函数返回一个新的函数,该函数在调用原始函数 fn
之前和之后添加日志记录。
缓存和 memoization
闭包可以用来实现缓存,避免重复计算。
js
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
// 判断是否存在
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
// 计算的值保存
cache.set(key, result);
return result;
};
}
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFibonacci = memoize(fibonacci);
console.log(memoizedFibonacci(10)); // 输出:55
console.log(memoizedFibonacci(10)); // 输出:55(从缓存中获取)
防抖和节流
防抖(Debounce)
js
function debounce(fn, delay) {
let timer = null; // 使用闭包存储定时器变量
return function () {
if (timer) clearTimeout(timer); // 如果定时器存在,清除上一次的定时器
timer = setTimeout(() => { // 设置新的定时器
fn.apply(this, arguments); // 延迟执行函数
}, delay);
};
}
节流(Throttle)
js
function throttle(fn, delay) {
let timer = 0;
return function() {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, arguments); // 延迟执行函数
timer = 0; // 执行完毕后重置计时器标志
}, delay);
}
}
发布-订阅模式
js
function createEventHub() {
let events = {}; // 使用闭包存储事件对象
// 订阅事件
function on(eventName, handler) {
events[eventName] = events[eventName] || [];
events[eventName].push(handler);
}
// 发布事件
function emit(eventName, ...args) {
const handlers = events[eventName];
if (!handlers) return;
handlers.forEach(handler => handler(...args));
}
return {
on,
emit
};
}
// 使用事件中心
const eventHub = createEventHub();
// 订阅事件
eventHub.on('login', (username) => {
console.log(`Welcome ${username}!`);
});
// 发布事件
eventHub.emit('login', 'John'); // 输出: "Welcome John!"
迭代器
js
function createIterator(arr) {
let index = 0;
return {
next: function() {
if (index < arr.length) {
return {
value: arr[index++],
done: false
};
} else {
return {
done: true
};
}
}
};
}
const myIterator = createIterator([1, 2, 3]);
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { done: true }
闭包的注意事项
- 内存泄漏:如果闭包不被正确管理,可能会导致内存泄漏,因为闭包会保持对外部作用域的引用,即使这些变量不再需要。
- 性能影响:闭包可能会增加内存使用和垃圾回收的负担,因为它们会保持对作用域链的引用。