JavaScript设计模式精要—单例、策略、装饰器与命令模式实战|新宇宙博客核心思想
实现 1:闭包惰性单例
function createSingleton(createFn) {
let instance = null;
return function(...args) {
if (!instance) {
instance = createFn.apply(this, args);
}
return instance;
};
}
class Database {
#pool;
constructor(config) {
this.#pool = createPool(config);
console.log('Database connection pool created');
}
query(sql) { return this.#pool.query(sql); }
}
const getDatabase = createSingleton((config) => new Database(config));
const db1 = getDatabase({ host: 'localhost' });
const db2 = getDatabase({ host: 'prod' });
console.log(db1 === db2);
实现 2:ES Module 单例(最简)
let state = { count: 0, users: [] };
export function getState() { return state; }
export function setState(partial) {
state = { ...state, ...partial };
}
实现 3:Proxy 实现不可多实例化
function makeSingleton(TargetClass) {
let instance = null;
return new Proxy(TargetClass, {
construct(target, args) {
if (!instance) {
instance = new target(...args);
}
return instance;
}
});
}
class Modal {
constructor(title) {
this.title = title;
}
show() { console.log(`显示 ${this.title}`); }
}
const SingleModal = makeSingleton(Modal);
const m1 = new SingleModal('登录弹窗');
const m2 = new SingleModal('注册弹窗');
console.log(m1 === m2);
console.log(m1.title);
策略模式(Strategy)
核心思想
将算法族封装成独立的策略对象,使它们可以互相替换,消除 if-else/switch。
场景:表单校验
function validate(value, type) {
if (type === 'required') {
return value.trim().length > 0 || '不能为空';
} else if (type === 'email') {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) || '邮箱格式错误';
} else if (type === 'mobile') {
return /^1[3-9]\d{9}$/.test(value) || '手机号格式错误';
}
}
const validators = {
required: (v) => v.trim().length > 0 || '不能为空',
email: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v) || '邮箱格式错误',
mobile: (v) => /^1[3-9]\d{9}$/.test(v) || '手机号格式错误',
minLen: (len) => (v) => v.length >= len || `最少 ${len} 个字符`,
maxLen: (len) => (v) => v.length <= len || `最多 ${len} 个字符`,
pattern: (re) => (v) => re.test(v) || '格式不匹配',
};
class Validator {
#rules = [];
add(field, ...strategies) {
this.#rules.push({ field, strategies });
return this;
}
validate(data) {
const errors = {};
for (const { field, strategies } of this.#rules) {
for (const strategy of strategies) {
const fn = typeof strategy === 'string' ? validators[strategy] : strategy;
const result = fn(data[field] ?? '');
if (result !== true) {
errors[field] = result;
break;
}
}
}
return { valid: Object.keys(errors).length === 0, errors };
}
}
const v = new Validator()
.add('username', 'required', validators.minLen(3), validators.maxLen(20))
.add('email', 'required', 'email')
.add('password', 'required', validators.minLen(8));
console.log(v.validate({ username: 'ab', email: 'invalid', password: '123' }));
场景:排序策略
const sortStrategies = {
byName: (a, b) => a.name.localeCompare(b.name),
byAge: (a, b) => a.age - b.age,
byPrice: (a, b) => a.price - b.price,
combined: (...fns) => (a, b) => {
for (const fn of fns) {
const r = fn(a, b);
if (r !== 0) return r;
}
return 0;
},
};
const users = [
{ name: 'Charlie', age: 25, price: 100 },
{ name: 'Alice', age: 30, price: 80 },
{ name: 'Bob', age: 25, price: 90 },
];
const sorted = [...users].sort(
sortStrategies.combined(sortStrategies.byAge, sortStrategies.byPrice)
);
装饰器模式(Decorator)与 AOP
函数装饰器(AOP)
function before(fn, beforeFn) {
return function(...args) {
beforeFn.apply(this, args);
return fn.apply(this, args);
};
}
function after(fn, afterFn) {
return function(...args) {
const result = fn.apply(this, args);
afterFn.call(this, result, ...args);
return result;
};
}
function around(fn, aroundFn) {
return function(...args) {
return aroundFn.call(this, fn.bind(this), ...args);
};
}
function login(username, password) {
console.log(`登录: ${username}`);
return { success: true, user: username };
}
const loggedLogin = before(
after(login, (result) => console.log('登录结果:', result)),
(username) => console.log('尝试登录用户:', username)
);
loggedLogin('alice', '123456');
const timedLogin = around(login, function(proceed, username, password) {
const start = performance.now();
const result = proceed(username, password);
console.log(`耗时: ${(performance.now() - start).toFixed(2)}ms`);
return result;
});
缓存装饰器(Memoize)
function memoize(fn, keyFn = (...args) => JSON.stringify(args)) {
const cache = new Map();
const memoized = function(...args) {
const key = keyFn(...args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
memoized.cache = cache;
memoized.clear = () => cache.clear();
return memoized;
}
function memoizeLRU(fn, maxSize = 100) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
const val = cache.get(key);
cache.delete(key);
cache.set(key, val);
return val;
}
const result = fn.apply(this, args);
if (cache.size >= maxSize) {
cache.delete(cache.keys().next().value);
}
cache.set(key, result);
return result;
};
}
ES 装饰器提案(Stage 3)
function log(target, context) {
const methodName = context.name;
return function(...args) {
console.log(`调用 ${methodName}(${args.join(', ')})`);
const result = target.apply(this, args);
console.log(`${methodName} 返回:`, result);
return result;
};
}
function readonly(target, context) {
return function(initialValue) {
Object.defineProperty(this, context.name, {
value: initialValue,
writable: false,
configurable: false,
});
return initialValue;
};
}
function singleton(Target) {
let instance;
return class extends Target {
constructor(...args) {
if (instance) return instance;
super(...args);
instance = this;
}
};
}
@singleton
class AppConfig {
@readonly
version = '1.0.0';
@log
getConfig(key) {
return this[key];
}
}
命令模式(Command)
核心思想
将操作封装成对象,支持撤销/重做、队列执行、日志记录。
撤销/重做系统
class CommandManager {
#history = [];
#redoStack = [];
execute(command) {
command.execute();
this.#history.push(command);
this.#redoStack = [];
return this;
}
undo() {
const command = this.#history.pop();
if (!command) return false;
command.undo();
this.#redoStack.push(command);
return true;
}
redo() {
const command = this.#redoStack.pop();
if (!command) return false;
command.execute();
this.#history.push(command);
return true;
}
canUndo() { return this.#history.length > 0; }
canRedo() { return this.#redoStack.length > 0; }
batch(...commands) {
return {
execute: () => commands.forEach(c => c.execute()),
undo: () => [...commands].reverse().forEach(c => c.undo()),
};
}
}
class InsertTextCommand {
constructor(document, position, text) {
this.doc = document;
this.pos = position;
this.text = text;
}
execute() {
this.doc.insert(this.pos, this.text);
}
undo() {
this.doc.delete(this.pos, this.text.length);
}
}
class DeleteTextCommand {
constructor(document, position, length) {
this.doc = document;
this.pos = position;
this.len = length;
this.deleted = '';
}
execute() {
this.deleted = this.doc.slice(this.pos, this.pos + this.len);
this.doc.delete(this.pos, this.len);
}
undo() {
this.doc.insert(this.pos, this.deleted);
}
}
const manager = new CommandManager();
const doc = new TextDocument();
manager.execute(new InsertTextCommand(doc, 0, 'Hello'));
manager.execute(new InsertTextCommand(doc, 5, ' World'));
console.log(doc.content);
manager.undo();
console.log(doc.content);
manager.redo();
console.log(doc.content);
迭代器模式(Iterator)
与 Symbol.iterator 的关联
class Range {
constructor(start, end, step = 1) {
this.start = start;
this.end = end;
this.step = step;
}
[Symbol.iterator]() {
let current = this.start;
const { end, step } = this;
return {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
},
[Symbol.iterator]() { return this; }
};
}
}
const range = new Range(1, 10, 2);
console.log([...range]);
console.log(Array.from(range));
for (const n of range) console.log(n);
const [first, second, ...rest] = new Range(0, 9);
console.log(first, second, rest);
无限迭代器与惰性求值
function* naturals(start = 0) {
let n = start;
while (true) yield n++;
}
class LazyIterator {
#source;
#ops = [];
constructor(iterable) {
this.#source = iterable;
}
map(fn) { this.#ops.push({ type: 'map', fn }); return this; }
filter(fn) { this.#ops.push({ type: 'filter', fn }); return this; }
take(n) { this.#ops.push({ type: 'take', n }); return this; }
*[Symbol.iterator]() {
let taken = 0;
const takeOp = this.#ops.find(op => op.type === 'take');
const limit = takeOp?.n ?? Infinity;
for (let value of this.#source) {
let current = value;
let skip = false;
for (const op of this.#ops) {
if (op.type === 'map') { current = op.fn(current); }
if (op.type === 'filter') { if (!op.fn(current)) { skip = true; break; } }
if (op.type === 'take') { }
}
if (!skip) {
yield current;
if (++taken >= limit) return;
}
}
}
toArray() { return [...this]; }
}
const result = new LazyIterator(naturals(1))
.filter(n => n % 2 === 0)
.map(n => n * n)
.take(5)
.toArray();
console.log(result);
观察者 vs 发布订阅
class Subject {
#observers = new Set();
subscribe(observer) { this.#observers.add(observer); }
unsubscribe(observer) { this.#observers.delete(observer); }
notify(data) {
this.#observers.forEach(obs => obs.update(data));
}
}
class EventBus {
#channels = new Map();
on(event, handler) {
if (!this.#channels.has(event)) {
this.#channels.set(event, new Set());
}
this.#channels.get(event).add(handler);
return () => this.off(event, handler);
}
off(event, handler) {
this.#channels.get(event)?.delete(handler);
}
emit(event, data) {
this.#channels.get(event)?.forEach(h => h(data));
}
once(event, handler) {
const wrapper = (data) => {
handler(data);
this.off(event, wrapper);
};
return this.on(event, wrapper);
}
}
手写验证:综合实战
AOP before/after/around 装饰器 + 日志系统
const AOP = {
before(fn, beforeFn) {
return function(...args) {
const result = beforeFn.apply(this, args);
if (result === false) return;
return fn.apply(this, args);
};
},
after(fn, afterFn) {
return function(...args) {
const result = fn.apply(this, args);
afterFn.call(this, result, ...args);
return result;
};
},
around(fn, aroundFn) {
return function(...args) {
return aroundFn.call(this, fn.bind(this), ...args);
};
},
afterAsync(fn, afterFn) {
return async function(...args) {
const result = await fn.apply(this, args);
await afterFn.call(this, result, ...args);
return result;
};
},
};
const withLog = (fn, name) => AOP.around(fn, async function(proceed, ...args) {
const id = Math.random().toString(36).slice(2, 8);
console.log(`[${id}] → ${name}(${args.map(JSON.stringify).join(', ')})`);
const start = performance.now();
try {
const result = await proceed(...args);
console.log(`[${id}] ← ${name} OK (${(performance.now()-start).toFixed(1)}ms)`, result);
return result;
} catch (e) {
console.error(`[${id}] ✗ ${name} ERROR: ${e.message}`);
throw e;
}
});
const withAuth = (fn, requiredRole) => AOP.before(fn, function() {
const user = getCurrentUser();
if (!user?.roles?.includes(requiredRole)) {
throw new Error(`权限不足,需要角色:${requiredRole}`);
}
});
const withRetry = (fn, maxRetries = 3, delay = 1000) =>
AOP.around(fn, async function(proceed, ...args) {
let lastError;
for (let i = 0; i <= maxRetries; i++) {
try {
return await proceed(...args);
} catch (e) {
lastError = e;
if (i < maxRetries) {
await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
}
}
}
throw lastError;
});
async function fetchUserData(userId) {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
const safeFetchUser = withLog(
withAuth(
withRetry(fetchUserData, 3, 500),
'user:read'
),
'fetchUserData'
);
深度追问
全局变量直接暴露,任何代码都可以修改;单例通过受控接口访问,支持惰性初始化、依赖注入和测试 mock。ES Module 的单例语义是最优雅的实现——模块缓存天然保证单例,无需额外代码。
React:组件本身就是策略,通过 props 传入不同"渲染策略"。高阶组件(HOC)是装饰器模式。React.lazy 是代理模式。
Vue:computed 的多种值来源(getter 函数 vs get/set 对象)是策略;指令修饰符(.stop、.prevent)是策略。
将一批命令用 batch() 包装,若执行到中途抛错,逆序调用所有已执行命令的 undo() 方法。实质上是数据库事务的 JS 实现版本——要么全部成功,要么全部回滚。
生成器函数返回的对象同时满足迭代器协议和可迭代协议(自带 Symbol.iterator 返回自身)。生成器是实现复杂迭代器的语法糖,无需手动管理状态机,yield 暂停和恢复由引擎处理。