闭包应用模式
2025年3月7日大约 10 分钟
闭包应用模式
闭包在JavaScript开发中有广泛的应用。本文将介绍闭包的常见应用模式,如模块模式、柯里化、记忆化、私有变量实现等,并通过实际案例展示闭包的强大功能。
模块模式
模块模式是JavaScript中最常用的闭包应用之一,它允许我们创建具有私有状态和方法的模块。
基本模块模式
const calculator = (function() {
// 私有变量和函数
let result = 0;
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
// 公开API
return {
add: function(a, b) {
result = add(a, b);
return result;
},
subtract: function(a, b) {
result = subtract(a, b);
return result;
},
getResult: function() {
return result;
},
reset: function() {
result = 0;
return result;
}
};
})();
// 使用模块
console.log(calculator.add(5, 3)); // 8
console.log(calculator.subtract(10, 4)); // 6
console.log(calculator.getResult()); // 6
calculator.reset(); // 0
在这个例子中:
- 立即执行函数表达式(IIFE)创建了一个闭包
result
、add
和subtract
是私有的,外部无法直接访问- 返回的对象包含公开的API,可以操作私有状态
揭示模块模式
揭示模块模式是模块模式的变体,它将所有功能定义为私有函数,然后只暴露需要公开的部分:
const revealingModule = (function() {
// 私有变量
let privateVar = 'I am private';
let publicVar = 'I am public';
// 私有函数
function privateFunction() {
return 'This is private';
}
function publicFunction() {
return 'This is public';
}
function showPrivate() {
return privateVar;
}
// 揭示公共API
return {
publicVar: publicVar,
publicFunction: publicFunction,
showPrivate: showPrivate
// privateFunction和privateVar不暴露
};
})();
console.log(revealingModule.publicVar); // "I am public"
console.log(revealingModule.showPrivate()); // "I am private"
console.log(revealingModule.privateVar); // undefined
揭示模块模式的优点是语法更加一致,所有函数都以相同的方式定义,然后选择性地暴露。
模块模式的实际应用
1. 数据服务模块
const userService = (function() {
// 私有数据和方法
const users = [];
let nextId = 1;
function findUserById(id) {
return users.find(user => user.id === id);
}
// 公开API
return {
addUser: function(name, email) {
const user = { id: nextId++, name, email };
users.push(user);
return user.id;
},
getUser: function(id) {
const user = findUserById(id);
if (user) {
// 返回副本以防止外部修改
return { ...user };
}
return null;
},
updateUser: function(id, updates) {
const user = findUserById(id);
if (user) {
Object.assign(user, updates);
return true;
}
return false;
},
deleteUser: function(id) {
const index = users.findIndex(user => user.id === id);
if (index !== -1) {
users.splice(index, 1);
return true;
}
return false;
},
getUserCount: function() {
return users.length;
}
};
})();
// 使用数据服务
const userId = userService.addUser('张三', 'zhangsan@example.com');
console.log(userService.getUser(userId)); // { id: 1, name: '张三', email: 'zhangsan@example.com' }
userService.updateUser(userId, { name: '张三丰' });
console.log(userService.getUser(userId)); // { id: 1, name: '张三丰', email: 'zhangsan@example.com' }
2. 配置管理模块
const configManager = (function() {
// 私有配置
const defaultConfig = {
theme: 'light',
fontSize: 16,
language: 'zh-CN'
};
let currentConfig = { ...defaultConfig };
// 验证配置项
function validateConfig(config) {
if (config.fontSize && (config.fontSize < 12 || config.fontSize > 24)) {
throw new Error('字体大小必须在12到24之间');
}
if (config.theme && !['light', 'dark', 'auto'].includes(config.theme)) {
throw new Error('主题必须是light、dark或auto');
}
return true;
}
// 公开API
return {
getConfig: function() {
return { ...currentConfig };
},
updateConfig: function(newConfig) {
try {
validateConfig(newConfig);
currentConfig = { ...currentConfig, ...newConfig };
return true;
} catch (error) {
console.error('配置更新失败:', error.message);
return false;
}
},
resetConfig: function() {
currentConfig = { ...defaultConfig };
return this.getConfig();
}
};
})();
// 使用配置管理器
console.log(configManager.getConfig()); // 获取当前配置
configManager.updateConfig({ theme: 'dark', fontSize: 18 });
console.log(configManager.getConfig()); // 更新后的配置
函数工厂与闭包
函数工厂是利用闭包创建定制化函数的模式。
基本函数工厂
function createMultiplier(factor) {
return function(number) {
return number * factor;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
const half = createMultiplier(0.5);
console.log(double(5)); // 10
console.log(triple(5)); // 15
console.log(half(5)); // 2.5
在这个例子中,createMultiplier
是一个函数工厂,它返回一个闭包,该闭包记住了factor
参数。
配置化的事件处理器
function createEventHandler(eventType, options) {
const { preventDefault, stopPropagation, callback } = options;
return function(event) {
if (preventDefault) {
event.preventDefault();
}
if (stopPropagation) {
event.stopPropagation();
}
callback(event);
};
}
// 使用函数工厂创建不同配置的事件处理器
const submitHandler = createEventHandler('submit', {
preventDefault: true,
stopPropagation: false,
callback: (event) => {
console.log('表单提交');
// 处理表单提交逻辑
}
});
const clickHandler = createEventHandler('click', {
preventDefault: false,
stopPropagation: true,
callback: (event) => {
console.log('元素点击');
// 处理点击逻辑
}
});
// 在实际应用中使用
document.getElementById('myForm').addEventListener('submit', submitHandler);
document.getElementById('myButton').addEventListener('click', clickHandler);
柯里化与偏函数应用
柯里化是将一个接受多个参数的函数转换为一系列接受单个参数的函数的技术。闭包是实现柯里化的关键。
基本柯里化
// 普通函数
function add(a, b, c) {
return a + b + c;
}
// 柯里化版本
function curriedAdd(a) {
return function(b) {
return function(c) {
return a + b + c;
};
};
}
console.log(add(1, 2, 3)); // 6
console.log(curriedAdd(1)(2)(3)); // 6
通用柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, args.concat(moreArgs));
};
};
}
// 使用通用柯里化函数
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
console.log(curriedAdd(1, 2, 3)); // 6
偏函数应用
偏函数应用是柯里化的一种变体,它允许我们固定函数的一部分参数,返回一个接受剩余参数的新函数。
function partial(fn, ...presetArgs) {
return function(...laterArgs) {
return fn.apply(this, [...presetArgs, ...laterArgs]);
};
}
function greet(greeting, name) {
return `${greeting}, ${name}!`;
}
const sayHello = partial(greet, '你好');
const sayGoodbye = partial(greet, '再见');
console.log(sayHello('张三')); // "你好, 张三!"
console.log(sayGoodbye('李四')); // "再见, 李四!"
实际应用:API请求函数
// 基础请求函数
function fetchAPI(baseURL, endpoint, method, data) {
const url = `${baseURL}${endpoint}`;
return fetch(url, {
method,
headers: {
'Content-Type': 'application/json'
},
body: data ? JSON.stringify(data) : undefined
}).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
});
}
// 使用偏函数创建特定API的请求函数
const userAPI = partial(fetchAPI, 'https://api.example.com', '/users');
// 创建特定方法的请求函数
const getUsers = partial(userAPI, 'GET');
const createUser = partial(userAPI, 'POST');
const updateUser = partial(userAPI, 'PUT');
// 使用
getUsers().then(users => console.log(users));
createUser({ name: '张三', email: 'zhangsan@example.com' })
.then(newUser => console.log(newUser));
updateUser({ id: 1, name: '张三丰' })
.then(updatedUser => console.log(updatedUser));
记忆化模式
记忆化是一种优化技术,通过缓存函数调用结果来避免重复计算。闭包可以用来实现记忆化。
基本记忆化函数
function memoize(fn) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
if (cache[key] === undefined) {
cache[key] = fn.apply(this, args);
}
return cache[key];
};
}
// 使用记忆化优化斐波那契函数
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
const memoizedFibonacci = memoize(function(n) {
if (n <= 1) return n;
return memoizedFibonacci(n - 1) + memoizedFibonacci(n - 2);
});
console.time('Regular');
console.log(fibonacci(35)); // 慢
console.timeEnd('Regular');
console.time('Memoized');
console.log(memoizedFibonacci(35)); // 快
console.timeEnd('Memoized');
带有过期时间的记忆化
function memoizeWithExpiration(fn, maxAge) {
const cache = {};
return function(...args) {
const key = JSON.stringify(args);
const now = Date.now();
if (cache[key] && now - cache[key].timestamp < maxAge) {
return cache[key].value;
}
const result = fn.apply(this, args);
cache[key] = {
value: result,
timestamp: now
};
return result;
};
}
// 模拟API调用
function fetchUserData(userId) {
console.log(`Fetching data for user ${userId}...`);
// 实际应用中这里会是一个真正的API调用
return { id: userId, name: `User ${userId}`, timestamp: Date.now() };
}
// 创建带有5秒过期时间的记忆化函数
const memoizedFetchUserData = memoizeWithExpiration(fetchUserData, 5000);
// 第一次调用 - 会执行实际函数
console.log(memoizedFetchUserData(1));
// 立即再次调用 - 会使用缓存
console.log(memoizedFetchUserData(1));
// 不同参数 - 会执行实际函数
console.log(memoizedFetchUserData(2));
// 等待5秒后再调用
setTimeout(() => {
// 缓存已过期 - 会重新执行实际函数
console.log(memoizedFetchUserData(1));
}, 5000);
记忆化的实际应用
记忆化在以下场景特别有用:
- 计算密集型函数:如递归计算、复杂数学运算
- 重复调用相同参数的函数:如UI渲染中的格式化函数
- API请求缓存:减少对相同数据的重复请求
// 实际应用:复杂数据处理
function processData(data, options) {
console.log('Processing data...');
// 假设这是一个复杂的数据处理函数
const result = data.map(item => {
// 复杂计算...
return item * options.factor + options.offset;
});
return result;
}
const memoizedProcessData = memoize(processData);
// 使用相同参数多次调用
const data = [1, 2, 3, 4, 5];
const options = { factor: 2, offset: 10 };
console.time('First call');
const result1 = memoizedProcessData(data, options);
console.timeEnd('First call'); // 较慢
console.time('Second call');
const result2 = memoizedProcessData(data, options);
console.timeEnd('Second call'); // 非常快,使用缓存
私有变量实现模式
闭包是在JavaScript中实现私有变量的主要方式之一。
基本私有变量模式
function createPerson(name, age) {
// 私有变量
let _name = name;
let _age = age;
// 返回公共接口
return {
getName: function() {
return _name;
},
getAge: function() {
return _age;
},
setName: function(newName) {
if (typeof newName === 'string' && newName.length > 0) {
_name = newName;
} else {
throw new Error('Invalid name');
}
},
setAge: function(newAge) {
if (typeof newAge === 'number' && newAge >= 0) {
_age = newAge;
} else {
throw new Error('Invalid age');
}
},
getInfo: function() {
return `${_name}, ${_age}岁`;
}
};
}
const person = createPerson('张三', 30);
console.log(person.getName()); // "张三"
console.log(person.getAge()); // 30
console.log(person.getInfo()); // "张三, 30岁"
person.setName('李四');
person.setAge(25);
console.log(person.getInfo()); // "李四, 25岁"
// 无法直接访问私有变量
console.log(person._name); // undefined
console.log(person._age); // undefined
构造函数与私有变量
function Person(name, age) {
// 私有变量
let _name = name;
let _age = age;
// 私有方法
function _validateAge(age) {
return typeof age === 'number' && age >= 0;
}
// 公共方法
this.getName = function() {
return _name;
};
this.getAge = function() {
return _age;
};
this.setName = function(newName) {
if (typeof newName === 'string' && newName.length > 0) {
_name = newName;
}
};
this.setAge = function(newAge) {
if (_validateAge(newAge)) {
_age = newAge;
}
};
}
const person = new Person('张三', 30);
console.log(person.getName()); // "张三"
person.setAge(35);
console.log(person.getAge()); // 35
实际应用:带验证的数据模型
function createUserModel(initialData) {
// 私有数据
let _data = {
id: null,
username: '',
email: '',
createdAt: new Date(),
...initialData
};
// 私有验证方法
function _validateEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function _validateUsername(username) {
return typeof username === 'string' && username.length >= 3;
}
// 返回公共接口
return {
getData: function() {
// 返回数据副本,防止外部直接修改
return { ..._data };
},
setUsername: function(username) {
if (_validateUsername(username)) {
_data.username = username;
return true;
}
return false;
},
setEmail: function(email) {
if (_validateEmail(email)) {
_data.email = email;
return true;
}
return false;
},
isValid: function() {
return _validateUsername(_data.username) &&
_validateEmail(_data.email);
},
toJSON: function() {
return { ..._data };
}
};
}
// 使用数据模型
const user = createUserModel({ id: 1, username: 'zhangsan' });
user.setEmail('zhangsan@example.com');
console.log(user.getData());
console.log('数据有效:', user.isValid());
迭代器与生成器模式
闭包可以用来创建自定义迭代器,维护迭代状态。
基本迭代器
function createIterator(array) {
let index = 0;
return {
next: function() {
if (index < array.length) {
return { value: array[index++], done: false };
} else {
return { done: true };
}
}
};
}
const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { done: true }
范围迭代器
function rangeIterator(start, end, step = 1) {
let current = start;
return {
next: function() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
} else {
return { done: true };
}
}
};
}
const range = rangeIterator(1, 10, 2);
let result = range.next();
while (!result.done) {
console.log(result.value); // 输出: 1, 3, 5, 7, 9
result = range.next();
}
使用闭包实现可迭代对象
function createRange(start, end, step = 1) {
return {
[Symbol.iterator]: function() {
let current = start;
return {
next: function() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
} else {
return { done: true };
}
}
};
}
};
}
// 可以在for...of循环中使用
const range = createRange(1, 10, 2);
for (const num of range) {
console.log(num); // 输出: 1, 3, 5, 7, 9
}
// 可以使用展开运算符
console.log([...range]); // [1, 3, 5, 7, 9]
异步控制流模式
闭包在异步编程中非常有用,可以帮助管理异步操作的状态和控制流。
简单的异步队列
function createAsyncQueue() {
const queue = [];
let isProcessing = false;
function processQueue() {
if (isProcessing || queue.length === 0) {
return;
}
isProcessing = true;
const task = queue.shift();
Promise.resolve(task())
.catch(error => console.error('Task error:', error))
.finally(() => {
isProcessing = false;
processQueue(); // 处理下一个任务
});
}
return {
addTask: function(task) {
queue.push(task);
processQueue();
},
getQueueLength: function() {
return queue.length;
}
};
}
// 使用异步队列
const queue = createAsyncQueue();
// 添加一些异步任务
queue.addTask(async () => {
console.log('Task 1 started');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Task 1 completed');
return 'Result 1';
});
queue.addTask(async () => {
console.log('Task 2 started');
await new Promise(resolve => setTimeout(resolve, 500));
console.log('Task 2 completed');
return 'Result 2';
});
console.log('Queue length:', queue.getQueueLength());
限制并发的异步操作
function createConcurrencyLimit(limit) {
let running = 0;
const queue = [];
function runNext() {
if (running >= limit || queue.length === 0) {
return;
}
running++;
const { task, resolve, reject } = queue.shift();
Promise.resolve(task())
.then(resolve)
.catch(reject)
.finally(() => {
running--;
runNext(); // 尝试运行下一个任务
});
}
return function runLimited(task) {
return new Promise((resolve, reject) => {
queue.push({ task, resolve, reject });
runNext();
});
};
}
// 使用并发限制
const runWithLimit = createConcurrencyLimit(2); // 最多同时运行2个任务
// 模拟一些异步任务
function createTask(id, delay) {
return async () => {
console.log(`Task ${id} started`);
await new Promise(resolve => setTimeout(resolve, delay));
console.log(`Task ${id} completed after ${delay}ms`);
return `Result ${id}`;
};
}
// 运行多个任务
async function runTasks() {
const tasks = [
createTask(1, 1000),
createTask(2, 500),
createTask(3, 1500),
createTask(4, 800),
createTask(5, 2000)
];
const promises = tasks.map(task => runWithLimit(task));
const results = await Promise.all(promises);
console.log('All tasks completed:', results);
}
runTasks();
总结
闭包是JavaScript中极其强大的特性,通过本文介绍的应用模式,我们可以看到闭包在实际开发中的多种用途:
- 模块模式:创建具有私有状态和公共API的模块
- 函数工厂:生成定制化的函数
- 柯里化与偏函数应用:转换函数调用方式,提高代码复用性
- 记忆化模式:优化性能,避免重复计算
- 私有变量实现:封装数据,提供受控访问
- 迭代器模式:管理迭代状态
- 异步控制流:管理异步操作的执行
掌握这些闭包应用模式,可以帮助我们编写更加模块化、可维护和高效的JavaScript代码。在实际开发中,这些模式往往会结合使用,形成更复杂的设计模式和架构。
理解闭包的工作原理和应用模式,是成为高级JavaScript开发者的重要一步。通过合理使用闭包,我们可以充分发挥JavaScript语言的灵活性和表现力。