闭包应用模式:实战中的最佳实践
闭包模式概述
掌握了闭包的基本概念后,更重要的是了解如何在实际项目中应用闭包。就像学会了砌砖的技术,接下来要学习如何建造不同类型的建筑——从简单的小屋到复杂的摩天大楼。本文将介绍 JavaScript 开发中常用的闭包应用模式,这些模式都是经过时间检验、广泛应用于真实项目中的最佳实践。
模块模式(Module Pattern)
模块模式是最常用的闭包应用之一,它利用闭包创建私有作用域,实现数据封装和信息隐藏。
基本模块模式
javascript
const UserModule = (function () {
// 私有变量
let users = [];
let currentId = 1;
// 私有方法
function generateId() {
return currentId++;
}
function validateUser(user) {
if (!user.name || user.name.trim() === "") {
throw new Error("User must have a name");
}
if (!user.email || !isValidEmail(user.email)) {
throw new Error("User must have a valid email");
}
return true;
}
function isValidEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
// 公共 API
return {
createUser(userData) {
const user = {
id: generateId(),
name: userData.name,
email: userData.email,
createdAt: new Date(),
};
validateUser(user);
users.push(user);
return user.id;
},
getUser(id) {
return users.find((user) => user.id === id);
},
updateUser(id, updates) {
const user = users.find((u) => u.id === id);
if (!user) {
throw new Error("User not found");
}
Object.assign(user, updates);
validateUser(user);
user.updatedAt = new Date();
return user;
},
deleteUser(id) {
const index = users.findIndex((u) => u.id === id);
if (index !== -1) {
users.splice(index, 1);
return true;
}
return false;
},
getAllUsers() {
return users.map((u) => ({ ...u })); // 返回副本
},
};
})();
// 使用模块
const userId = UserModule.createUser({
name: "Sarah Johnson",
email: "[email protected]",
});
console.log(UserModule.getUser(userId));
// { id: 1, name: 'Sarah Johnson', email: '[email protected]', ... }
// 私有成员无法访问
// console.log(users); // ReferenceError
// UserModule.generateId(); // TypeError: not a function揭示模块模式(Revealing Module Pattern)
这是模块模式的一个变体,所有方法都在私有作用域中定义,最后统一暴露:
javascript
const Calculator = (function () {
// 私有状态
let history = [];
let precision = 2;
// 私有辅助函数
function round(number) {
return Number(number.toFixed(precision));
}
function recordOperation(operation, operands, result) {
history.push({
operation,
operands,
result,
timestamp: new Date(),
});
}
// 公共方法(都在私有作用域中定义)
function add(a, b) {
const result = round(a + b);
recordOperation("add", [a, b], result);
return result;
}
function subtract(a, b) {
const result = round(a - b);
recordOperation("subtract", [a, b], result);
return result;
}
function multiply(a, b) {
const result = round(a * b);
recordOperation("multiply", [a, b], result);
return result;
}
function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero");
}
const result = round(a / b);
recordOperation("divide", [a, b], result);
return result;
}
function getHistory() {
return [...history];
}
function clearHistory() {
history = [];
}
function setPrecision(value) {
if (value < 0 || value > 10) {
throw new Error("Precision must be between 0 and 10");
}
precision = value;
}
// 揭示公共接口
return {
add,
subtract,
multiply,
divide,
getHistory,
clearHistory,
setPrecision,
};
})();
// 使用
Calculator.add(10, 5); // 15
Calculator.multiply(3, 7); // 21
Calculator.divide(10, 3); // 3.33
console.log(Calculator.getHistory());
// [
// { operation: 'add', operands: [10, 5], result: 15, ... },
// { operation: 'multiply', operands: [3, 7], result: 21, ... },
// { operation: 'divide', operands: [10, 3], result: 3.33, ... }
// ]模块的增强和扩展
可以通过传递模块本身来增强现有模块:
javascript
const TaskManager = (function () {
let tasks = [];
return {
addTask(task) {
tasks.push(task);
},
getTasks() {
return [...tasks];
},
};
})();
// 扩展模块
const TaskManager_Extended = (function (module) {
// 保存原有的私有状态
let filterCache = new Map();
// 添加新方法
module.filterTasks = function (predicate) {
const key = predicate.toString();
if (filterCache.has(key)) {
return filterCache.get(key);
}
const result = this.getTasks().filter(predicate);
filterCache.set(key, result);
return result;
};
module.clearCache = function () {
filterCache.clear();
};
return module;
})(TaskManager);
// 使用扩展后的模块
TaskManager_Extended.addTask({ title: "Task 1", completed: false });
TaskManager_Extended.addTask({ title: "Task 2", completed: true });
const incompleteTasks = TaskManager_Extended.filterTasks((t) => !t.completed);
console.log(incompleteTasks); // [{ title: 'Task 1', completed: false }]单例模式(Singleton Pattern)
利用闭包确保一个类只有一个实例:
javascript
const DatabaseConnection = (function () {
let instance;
function createConnection(config) {
// 私有状态
const connectionId = Math.random().toString(36).substr(2, 9);
const createdAt = new Date();
let isConnected = false;
let queryCount = 0;
// 私有方法
function log(message) {
console.log(`[${connectionId}] ${message}`);
}
// 公共接口
return {
connect() {
if (isConnected) {
log("Already connected");
return;
}
log(`Connecting to ${config.host}:${config.port}`);
isConnected = true;
},
disconnect() {
if (!isConnected) {
log("Already disconnected");
return;
}
log("Disconnecting...");
isConnected = false;
},
query(sql) {
if (!isConnected) {
throw new Error("Not connected to database");
}
queryCount++;
log(`Executing query #${queryCount}: ${sql}`);
// 模拟查询
return { success: true, rows: [] };
},
getStats() {
return {
connectionId,
createdAt,
isConnected,
queryCount,
};
},
};
}
return {
getInstance(config) {
if (!instance) {
instance = createConnection(config);
}
return instance;
},
resetInstance() {
instance = null;
},
};
})();
// 使用单例
const db1 = DatabaseConnection.getInstance({
host: "localhost",
port: 5432,
});
const db2 = DatabaseConnection.getInstance({
host: "remotehost", // 这个配置会被忽略
port: 3306,
});
console.log(db1 === db2); // true - 同一个实例
db1.connect();
db1.query("SELECT * FROM users");
console.log(db1.getStats());
console.log(db2.getStats()); // 相同的统计信息工厂模式(Factory Pattern)
使用闭包创建对象工厂:
javascript
function createUserFactory(defaultRole) {
// 工厂级别的私有状态
let userCount = 0;
const createdUsers = [];
// 工厂级别的私有方法
function generateUsername(name) {
const baseName = name.toLowerCase().replace(/\s+/g, "");
return `${baseName}_${Date.now()}`;
}
function trackUser(user) {
createdUsers.push({
id: user.id,
createdAt: new Date(),
});
}
// 返回工厂函数
return function createUser(name, customRole) {
userCount++;
const user = {
id: userCount,
name,
username: generateUsername(name),
role: customRole || defaultRole,
// 实例方法(每个用户都有)
getProfile() {
return {
id: this.id,
name: this.name,
username: this.username,
role: this.role,
};
},
hasPermission(permission) {
const rolePermissions = {
admin: ["read", "write", "delete"],
user: ["read"],
guest: [],
};
return rolePermissions[this.role]?.includes(permission) || false;
},
};
trackUser(user);
return user;
};
}
// 创建不同的工厂
const createAdmin = createUserFactory("admin");
const createRegularUser = createUserFactory("user");
const admin = createAdmin("John Smith");
const user1 = createRegularUser("Sarah Wilson");
const user2 = createRegularUser("Michael Brown");
console.log(admin.hasPermission("delete")); // true
console.log(user1.hasPermission("delete")); // false
console.log(user2.hasPermission("read")); // true链式调用模式(Method Chaining)
通过返回函数自身实现链式调用:
javascript
function createQueryBuilder(tableName) {
// 私有状态
let query = {
table: tableName,
select: [],
where: [],
orderBy: [],
limit: null,
};
const builder = {
SELECT(...fields) {
query.select.push(...fields);
return this; // 返回自身实现链式调用
},
WHERE(condition) {
query.where.push(condition);
return this;
},
AND(condition) {
if (query.where.length === 0) {
throw new Error("Cannot use AND without WHERE");
}
query.where.push(`AND ${condition}`);
return this;
},
OR(condition) {
if (query.where.length === 0) {
throw new Error("Cannot use OR without WHERE");
}
query.where.push(`OR ${condition}`);
return this;
},
ORDER_BY(field, direction = "ASC") {
query.orderBy.push(`${field} ${direction}`);
return this;
},
LIMIT(count) {
query.limit = count;
return this;
},
build() {
let sql = "SELECT ";
sql += query.select.length > 0 ? query.select.join(", ") : "*";
sql += ` FROM ${query.table}`;
if (query.where.length > 0) {
sql += " WHERE " + query.where.join(" ");
}
if (query.orderBy.length > 0) {
sql += " ORDER BY " + query.orderBy.join(", ");
}
if (query.limit) {
sql += ` LIMIT ${query.limit}`;
}
return sql;
},
reset() {
query = {
table: tableName,
select: [],
where: [],
orderBy: [],
limit: null,
};
return this;
},
};
return builder;
}
// 使用链式调用
const userQuery = createQueryBuilder("users")
.SELECT("id", "name", "email")
.WHERE("age > 18")
.AND('status = "active"')
.OR('role = "admin"')
.ORDER_BY("created_at", "DESC")
.LIMIT(10)
.build();
console.log(userQuery);
// SELECT id, name, email FROM users
// WHERE age > 18 AND status = "active" OR role = "admin"
// ORDER BY created_at DESC LIMIT 10柯里化模式(Currying)
将多参数函数转换为一系列单参数函数:
javascript
// 通用柯里化函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function (...nextArgs) {
return curried.apply(this, [...args, ...nextArgs]);
};
};
}
// 实际应用示例
const log = curry(function (level, timestamp, message) {
console.log(`[${level}] ${timestamp}: ${message}`);
});
// 创建专用的日志函数
const error = log("ERROR");
const errorNow = error(new Date().toISOString());
errorNow("Database connection failed");
errorNow("Invalid user input");
const info = log("INFO");
const infoNow = info(new Date().toISOString());
infoNow("User logged in");
infoNow("Task completed");
// 也可以一次传入所有参数
log("WARN", new Date().toISOString(), "Cache is full");
// 更实用的例子:创建配置化的函数
const createEmailValidator = curry(function (domain, minLength, email) {
if (email.length < minLength) {
return false;
}
return email.endsWith(`@${domain}`);
});
const isCompanyEmail = createEmailValidator("company.com")(5);
console.log(isCompanyEmail("[email protected]")); // true
console.log(isCompanyEmail("[email protected]")); // false (太短)
console.log(isCompanyEmail("[email protected]")); // false (域名不匹配)部分应用模式(Partial Application)
固定函数的部分参数:
javascript
function partial(fn, ...fixedArgs) {
return function (...remainingArgs) {
return fn.apply(this, [...fixedArgs, ...remainingArgs]);
};
}
// 使用示例
function createNotification(type, title, message, options) {
return {
type,
title,
message,
timestamp: new Date(),
...options,
};
}
// 创建特定类型的通知函数
const createError = partial(createNotification, "error");
const createWarning = partial(createNotification, "warning");
const createSuccess = partial(createNotification, "success");
// 创建更具体的函数
const createErrorWithTitle = partial(createError, "Error Occurred");
// 使用
const notification1 = createErrorWithTitle("Failed to save data", {
dismissible: true,
});
const notification2 = createSuccess(
"Operation Complete",
"Data saved successfully",
{ autoClose: 3000 }
);
console.log(notification1);
// { type: 'error', title: 'Error Occurred', message: 'Failed to save data', ... }
console.log(notification2);
// { type: 'success', title: 'Operation Complete', ... }装饰器模式(Decorator Pattern)
使用闭包包装和增强现有函数:
javascript
// 性能测量装饰器
function measurePerformance(fn, label) {
return function (...args) {
const start = performance.now();
const result = fn.apply(this, args);
const end = performance.now();
console.log(`${label || fn.name} took ${(end - start).toFixed(2)}ms`);
return result;
};
}
// 缓存装饰器
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log("Returning cached result");
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 重试装饰器
function retry(fn, maxAttempts = 3, delay = 1000) {
return async function (...args) {
let lastError;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await fn.apply(this, args);
} catch (error) {
lastError = error;
console.log(`Attempt ${attempt} failed: ${error.message}`);
if (attempt < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
}
throw new Error(
`Failed after ${maxAttempts} attempts: ${lastError.message}`
);
};
}
// 日志装饰器
function logCalls(fn) {
return function (...args) {
console.log(`Calling ${fn.name} with args:`, args);
const result = fn.apply(this, args);
console.log(`${fn.name} returned:`, result);
return result;
};
}
// 使用装饰器
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 组合多个装饰器
const optimizedFib = memoize(measurePerformance(fibonacci, "Fibonacci"));
console.log(optimizedFib(40)); // 第一次,计算并记录时间
console.log(optimizedFib(40)); // 第二次,从缓存返回观察者模式(Observer Pattern)
使用闭包实现发布-订阅模式:
javascript
function createEventEmitter() {
// 私有状态
const events = new Map();
const onceListeners = new WeakMap();
return {
on(event, callback) {
if (!events.has(event)) {
events.set(event, []);
}
events.get(event).push(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
},
once(event, callback) {
const wrapped = (...args) => {
callback.apply(this, args);
this.off(event, wrapped);
};
onceListeners.set(wrapped, callback);
this.on(event, wrapped);
return () => this.off(event, wrapped);
},
off(event, callback) {
if (!events.has(event)) return;
const callbacks = events.get(event);
const index = callbacks.findIndex((cb) => {
return cb === callback || onceListeners.get(cb) === callback;
});
if (index !== -1) {
callbacks.splice(index, 1);
}
},
emit(event, ...args) {
if (!events.has(event)) return;
const callbacks = [...events.get(event)];
callbacks.forEach((callback) => {
callback.apply(this, args);
});
},
removeAllListeners(event) {
if (event) {
events.delete(event);
} else {
events.clear();
}
},
listenerCount(event) {
return events.has(event) ? events.get(event).length : 0;
},
};
}
// 使用
const emitter = createEventEmitter();
// 订阅事件
const unsubscribe = emitter.on("user:login", (user) => {
console.log(`User logged in: ${user.name}`);
});
emitter.once("app:ready", () => {
console.log("App is ready!");
});
// 触发事件
emitter.emit("user:login", { name: "Sarah" });
emitter.emit("user:login", { name: "John" });
emitter.emit("app:ready"); // 只触发一次
emitter.emit("app:ready"); // 不会触发
// 取消订阅
unsubscribe();
emitter.emit("user:login", { name: "Michael" }); // 不会触发状态机模式(State Machine)
使用闭包管理状态转换:
javascript
function createStateMachine(initialState, transitions) {
let currentState = initialState;
const listeners = new Map();
function canTransition(toState) {
const allowedTransitions = transitions[currentState];
return allowedTransitions && allowedTransitions.includes(toState);
}
function notifyListeners(from, to) {
const callbacks = listeners.get("transition") || [];
callbacks.forEach((cb) => cb({ from, to, timestamp: new Date() }));
}
return {
getState() {
return currentState;
},
transition(toState) {
if (!canTransition(toState)) {
throw new Error(
`Invalid transition from "${currentState}" to "${toState}"`
);
}
const from = currentState;
currentState = toState;
notifyListeners(from, toState);
return currentState;
},
canTransitionTo(toState) {
return canTransition(toState);
},
onTransition(callback) {
if (!listeners.has("transition")) {
listeners.set("transition", []);
}
listeners.get("transition").push(callback);
return () => {
const callbacks = listeners.get("transition");
const index = callbacks.indexOf(callback);
if (index !== -1) {
callbacks.splice(index, 1);
}
};
},
getAvailableTransitions() {
return transitions[currentState] || [];
},
};
}
// 使用状态机
const orderStateMachine = createStateMachine("pending", {
pending: ["processing", "cancelled"],
processing: ["shipped", "cancelled"],
shipped: ["delivered", "returned"],
delivered: ["returned"],
cancelled: [],
returned: [],
});
// 监听状态变化
orderStateMachine.onTransition((transition) => {
console.log(`Order ${transition.from} -> ${transition.to}`);
});
// 执行状态转换
console.log(orderStateMachine.getState()); // "pending"
orderStateMachine.transition("processing");
orderStateMachine.transition("shipped");
orderStateMachine.transition("delivered");
// 检查可用转换
console.log(orderStateMachine.getAvailableTransitions()); // ['returned']
// 尝试无效转换
try {
orderStateMachine.transition("processing"); // 错误!
} catch (error) {
console.error(error.message);
}命令模式(Command Pattern)
将操作封装为对象,支持撤销/重做:
javascript
function createCommandManager() {
const history = [];
let currentIndex = -1;
return {
execute(command) {
// 执行命令
command.execute();
// 清除当前位置之后的历史
history.splice(currentIndex + 1);
// 添加到历史
history.push(command);
currentIndex++;
},
undo() {
if (currentIndex >= 0) {
const command = history[currentIndex];
command.undo();
currentIndex--;
return true;
}
return false;
},
redo() {
if (currentIndex < history.length - 1) {
currentIndex++;
const command = history[currentIndex];
command.execute();
return true;
}
return false;
},
canUndo() {
return currentIndex >= 0;
},
canRedo() {
return currentIndex < history.length - 1;
},
getHistory() {
return history.map((cmd, index) => ({
type: cmd.type,
isCurrent: index === currentIndex,
}));
},
};
}
// 创建命令工厂
function createCommand(type, execute, undo) {
return { type, execute, undo };
}
// 使用示例:文本编辑器
const editor = {
content: "",
insert(text) {
this.content += text;
},
delete(length) {
this.content = this.content.slice(0, -length);
},
getContent() {
return this.content;
},
};
const commandManager = createCommandManager();
// 创建插入命令
function createInsertCommand(text) {
return createCommand(
"insert",
() => editor.insert(text),
() => editor.delete(text.length)
);
}
// 创建删除命令
function createDeleteCommand(length) {
let deletedText = "";
return createCommand(
"delete",
() => {
deletedText = editor.content.slice(-length);
editor.delete(length);
},
() => editor.insert(deletedText)
);
}
// 使用命令
commandManager.execute(createInsertCommand("Hello"));
console.log(editor.getContent()); // "Hello"
commandManager.execute(createInsertCommand(" World"));
console.log(editor.getContent()); // "Hello World"
commandManager.execute(createDeleteCommand(6));
console.log(editor.getContent()); // "Hello"
// 撤销
commandManager.undo();
console.log(editor.getContent()); // "Hello World"
// 重做
commandManager.redo();
console.log(editor.getContent()); // "Hello"
console.log(commandManager.getHistory());
// [
// { type: 'insert', isCurrent: false },
// { type: 'insert', isCurrent: false },
// { type: 'delete', isCurrent: true }
// ]总结
闭包的应用模式展示了 JavaScript 的强大和灵活性。通过这些模式,我们可以:
- 模块模式:创建私有作用域,实现数据封装
- 单例模式:确保只有一个实例存在
- 工厂模式:创建对象的标准化方式
- 链式调用:提供流畅的 API 接口
- 柯里化:实现函数的部分应用和复用
- 装饰器:不修改原函数的情况下增强功能
- 观察者:实现事件驱动的架构
- 状态机:管理复杂的状态转换
- 命令模式:封装操作,支持撤销/重做
这些模式不仅是理论知识,更是在实际项目中久经考验的最佳实践。掌握它们能让你的代码更加优雅、可维护和可扩展。在下一篇文章中,我们将探讨闭包可能带来的内存泄漏问题,以及如何有效地避免和解决这些问题。