- 发布于
JavaScript设计模式实战:从经典模式到现代应用架构
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
JavaScript设计模式实战:从经典模式到现代应用架构
设计模式是解决常见编程问题的可复用解决方案。本文将深入探讨JavaScript中的经典设计模式和现代应用架构模式。
创建型模式
单例模式 (Singleton Pattern)
// 经典单例模式实现
class Singleton {
constructor() {
if (Singleton.instance) {
return Singleton.instance
}
this.data = {}
this.timestamp = Date.now()
Singleton.instance = this
return this
}
getData(key) {
return this.data[key]
}
setData(key, value) {
this.data[key] = value
}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton()
}
return Singleton.instance
}
}
// 使用示例
const instance1 = new Singleton()
const instance2 = new Singleton()
const instance3 = Singleton.getInstance()
console.log(instance1 === instance2) // true
console.log(instance2 === instance3) // true
// 现代化的单例模式 - 使用模块
const ConfigManager = (() => {
let instance = null
class Config {
constructor() {
this.settings = {
apiUrl: 'https://api.example.com',
timeout: 5000,
retries: 3
}
}
get(key) {
return this.settings[key]
}
set(key, value) {
this.settings[key] = value
}
update(newSettings) {
this.settings = { ...this.settings, ...newSettings }
}
}
return {
getInstance() {
if (!instance) {
instance = new Config()
}
return instance
}
}
})()
// 应用配置管理器
const config = ConfigManager.getInstance()
config.set('theme', 'dark')
console.log(config.get('theme')) // 'dark'
// 数据库连接单例
class DatabaseConnection {
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance
}
this.connection = null
this.isConnected = false
DatabaseConnection.instance = this
}
async connect(connectionString) {
if (this.isConnected) {
return this.connection
}
try {
// 模拟数据库连接
this.connection = await this.createConnection(connectionString)
this.isConnected = true
console.log('Database connected')
return this.connection
} catch (error) {
console.error('Database connection failed:', error)
throw error
}
}
async createConnection(connectionString) {
// 模拟异步连接过程
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: Math.random(), connectionString })
}, 1000)
})
}
async disconnect() {
if (this.isConnected) {
this.connection = null
this.isConnected = false
console.log('Database disconnected')
}
}
getConnection() {
if (!this.isConnected) {
throw new Error('Database not connected')
}
return this.connection
}
}
// 使用数据库连接单例
const db1 = new DatabaseConnection()
const db2 = new DatabaseConnection()
console.log(db1 === db2) // true
工厂模式 (Factory Pattern)
// 简单工厂模式
class ButtonFactory {
static createButton(type, props = {}) {
switch (type) {
case 'primary':
return new PrimaryButton(props)
case 'secondary':
return new SecondaryButton(props)
case 'danger':
return new DangerButton(props)
default:
throw new Error(`Unknown button type: ${type}`)
}
}
}
class Button {
constructor(props) {
this.text = props.text || 'Button'
this.onClick = props.onClick || (() => {})
}
render() {
const button = document.createElement('button')
button.textContent = this.text
button.addEventListener('click', this.onClick)
return button
}
}
class PrimaryButton extends Button {
render() {
const button = super.render()
button.className = 'btn btn-primary'
return button
}
}
class SecondaryButton extends Button {
render() {
const button = super.render()
button.className = 'btn btn-secondary'
return button
}
}
class DangerButton extends Button {
render() {
const button = super.render()
button.className = 'btn btn-danger'
return button
}
}
// 使用工厂创建按钮
const primaryBtn = ButtonFactory.createButton('primary', {
text: 'Save',
onClick: () => console.log('Saving...')
})
const dangerBtn = ButtonFactory.createButton('danger', {
text: 'Delete',
onClick: () => console.log('Deleting...')
})
// 抽象工厂模式
class UIFactory {
createButton() {
throw new Error('createButton method must be implemented')
}
createInput() {
throw new Error('createInput method must be implemented')
}
}
class MaterialUIFactory extends UIFactory {
createButton(props) {
return new MaterialButton(props)
}
createInput(props) {
return new MaterialInput(props)
}
}
class BootstrapUIFactory extends UIFactory {
createButton(props) {
return new BootstrapButton(props)
}
createInput(props) {
return new BootstrapInput(props)
}
}
// UI组件基类
class UIComponent {
constructor(props) {
this.props = props
}
}
class MaterialButton extends UIComponent {
render() {
const button = document.createElement('button')
button.className = 'mdc-button mdc-button--raised'
button.textContent = this.props.text
return button
}
}
class BootstrapButton extends UIComponent {
render() {
const button = document.createElement('button')
button.className = 'btn btn-primary'
button.textContent = this.props.text
return button
}
}
class MaterialInput extends UIComponent {
render() {
const input = document.createElement('input')
input.className = 'mdc-text-field__input'
input.placeholder = this.props.placeholder
return input
}
}
class BootstrapInput extends UIComponent {
render() {
const input = document.createElement('input')
input.className = 'form-control'
input.placeholder = this.props.placeholder
return input
}
}
// 使用抽象工厂
const uiTheme = 'material' // 或 'bootstrap'
const factory = uiTheme === 'material'
? new MaterialUIFactory()
: new BootstrapUIFactory()
const button = factory.createButton({ text: 'Click me' })
const input = factory.createInput({ placeholder: 'Enter text' })
// HTTP客户端工厂
class HttpClientFactory {
static createClient(type, config = {}) {
switch (type) {
case 'fetch':
return new FetchClient(config)
case 'xhr':
return new XHRClient(config)
case 'axios':
return new AxiosClient(config)
default:
return new FetchClient(config)
}
}
}
class HttpClient {
constructor(config) {
this.baseURL = config.baseURL || ''
this.timeout = config.timeout || 5000
this.headers = config.headers || {}
}
async get(url) {
throw new Error('get method must be implemented')
}
async post(url, data) {
throw new Error('post method must be implemented')
}
}
class FetchClient extends HttpClient {
async get(url) {
const response = await fetch(`${this.baseURL}${url}`, {
headers: this.headers,
signal: AbortSignal.timeout(this.timeout)
})
return response.json()
}
async post(url, data) {
const response = await fetch(`${this.baseURL}${url}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...this.headers
},
body: JSON.stringify(data),
signal: AbortSignal.timeout(this.timeout)
})
return response.json()
}
}
class XHRClient extends HttpClient {
async get(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', `${this.baseURL}${url}`)
Object.entries(this.headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value)
})
xhr.timeout = this.timeout
xhr.onload = () => resolve(JSON.parse(xhr.responseText))
xhr.onerror = () => reject(new Error('Request failed'))
xhr.ontimeout = () => reject(new Error('Request timeout'))
xhr.send()
})
}
async post(url, data) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('POST', `${this.baseURL}${url}`)
xhr.setRequestHeader('Content-Type', 'application/json')
Object.entries(this.headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value)
})
xhr.timeout = this.timeout
xhr.onload = () => resolve(JSON.parse(xhr.responseText))
xhr.onerror = () => reject(new Error('Request failed'))
xhr.ontimeout = () => reject(new Error('Request timeout'))
xhr.send(JSON.stringify(data))
})
}
}
// 使用HTTP客户端工厂
const httpClient = HttpClientFactory.createClient('fetch', {
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Authorization': 'Bearer token123'
}
})
// 使用客户端
httpClient.get('/users').then(users => console.log(users))
建造者模式 (Builder Pattern)
// 查询构建器
class QueryBuilder {
constructor() {
this.query = {
select: [],
from: '',
where: [],
orderBy: [],
limit: null,
offset: null
}
}
select(fields) {
if (Array.isArray(fields)) {
this.query.select.push(...fields)
} else {
this.query.select.push(fields)
}
return this
}
from(table) {
this.query.from = table
return this
}
where(condition) {
this.query.where.push(condition)
return this
}
orderBy(field, direction = 'ASC') {
this.query.orderBy.push(`${field} ${direction}`)
return this
}
limit(count) {
this.query.limit = count
return this
}
offset(count) {
this.query.offset = count
return this
}
build() {
let sql = `SELECT ${this.query.select.join(', ')} FROM ${this.query.from}`
if (this.query.where.length > 0) {
sql += ` WHERE ${this.query.where.join(' AND ')}`
}
if (this.query.orderBy.length > 0) {
sql += ` ORDER BY ${this.query.orderBy.join(', ')}`
}
if (this.query.limit) {
sql += ` LIMIT ${this.query.limit}`
}
if (this.query.offset) {
sql += ` OFFSET ${this.query.offset}`
}
return sql
}
}
// 使用查询构建器
const query = new QueryBuilder()
.select(['id', 'name', 'email'])
.from('users')
.where('age > 18')
.where('status = "active"')
.orderBy('created_at', 'DESC')
.limit(10)
.offset(20)
.build()
console.log(query)
// SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY created_at DESC LIMIT 10 OFFSET 20
// HTTP请求构建器
class RequestBuilder {
constructor() {
this.config = {
method: 'GET',
url: '',
headers: {},
params: {},
data: null,
timeout: 5000
}
}
method(method) {
this.config.method = method.toUpperCase()
return this
}
url(url) {
this.config.url = url
return this
}
header(key, value) {
this.config.headers[key] = value
return this
}
headers(headers) {
Object.assign(this.config.headers, headers)
return this
}
param(key, value) {
this.config.params[key] = value
return this
}
params(params) {
Object.assign(this.config.params, params)
return this
}
data(data) {
this.config.data = data
return this
}
timeout(timeout) {
this.config.timeout = timeout
return this
}
json(data) {
this.config.data = JSON.stringify(data)
this.config.headers['Content-Type'] = 'application/json'
return this
}
auth(token) {
this.config.headers['Authorization'] = `Bearer ${token}`
return this
}
async send() {
const url = new URL(this.config.url)
// 添加查询参数
Object.entries(this.config.params).forEach(([key, value]) => {
url.searchParams.append(key, value)
})
const fetchConfig = {
method: this.config.method,
headers: this.config.headers,
signal: AbortSignal.timeout(this.config.timeout)
}
if (this.config.data && this.config.method !== 'GET') {
fetchConfig.body = this.config.data
}
const response = await fetch(url.toString(), fetchConfig)
return response.json()
}
}
// 使用请求构建器
const response = await new RequestBuilder()
.method('POST')
.url('https://api.example.com/users')
.auth('your-token-here')
.json({ name: 'John', email: 'john@example.com' })
.timeout(10000)
.send()
// 表单构建器
class FormBuilder {
constructor() {
this.form = document.createElement('form')
this.fields = []
}
addField(type, name, options = {}) {
const field = this.createField(type, name, options)
this.fields.push(field)
return this
}
createField(type, name, options) {
const wrapper = document.createElement('div')
wrapper.className = 'form-group'
if (options.label) {
const label = document.createElement('label')
label.textContent = options.label
label.setAttribute('for', name)
wrapper.appendChild(label)
}
let input
if (type === 'textarea') {
input = document.createElement('textarea')
} else if (type === 'select') {
input = document.createElement('select')
if (options.options) {
options.options.forEach(opt => {
const option = document.createElement('option')
option.value = opt.value
option.textContent = opt.text
input.appendChild(option)
})
}
} else {
input = document.createElement('input')
input.type = type
}
input.name = name
input.id = name
if (options.placeholder) {
input.placeholder = options.placeholder
}
if (options.required) {
input.required = true
}
if (options.value) {
input.value = options.value
}
wrapper.appendChild(input)
return wrapper
}
addSubmitButton(text = 'Submit') {
const button = document.createElement('button')
button.type = 'submit'
button.textContent = text
button.className = 'btn btn-primary'
this.fields.push(button)
return this
}
onSubmit(handler) {
this.form.addEventListener('submit', handler)
return this
}
build() {
this.fields.forEach(field => {
this.form.appendChild(field)
})
return this.form
}
}
// 使用表单构建器
const form = new FormBuilder()
.addField('text', 'name', {
label: 'Name',
placeholder: 'Enter your name',
required: true
})
.addField('email', 'email', {
label: 'Email',
placeholder: 'Enter your email',
required: true
})
.addField('select', 'country', {
label: 'Country',
options: [
{ value: 'us', text: 'United States' },
{ value: 'uk', text: 'United Kingdom' },
{ value: 'ca', text: 'Canada' }
]
})
.addField('textarea', 'message', {
label: 'Message',
placeholder: 'Enter your message'
})
.addSubmitButton('Send Message')
.onSubmit((e) => {
e.preventDefault()
const formData = new FormData(e.target)
console.log(Object.fromEntries(formData))
})
.build()
document.body.appendChild(form)
行为型模式
观察者模式 (Observer Pattern)
// 事件发射器实现
class EventEmitter {
constructor() {
this.events = new Map()
}
on(event, listener) {
if (!this.events.has(event)) {
this.events.set(event, [])
}
this.events.get(event).push(listener)
// 返回取消订阅函数
return () => this.off(event, listener)
}
once(event, listener) {
const onceWrapper = (...args) => {
listener(...args)
this.off(event, onceWrapper)
}
return this.on(event, onceWrapper)
}
off(event, listener) {
if (!this.events.has(event)) return
const listeners = this.events.get(event)
const index = listeners.indexOf(listener)
if (index > -1) {
listeners.splice(index, 1)
}
if (listeners.length === 0) {
this.events.delete(event)
}
}
emit(event, ...args) {
if (!this.events.has(event)) return
const listeners = this.events.get(event).slice() // 复制数组避免修改问题
listeners.forEach(listener => {
try {
listener(...args)
} catch (error) {
console.error(`Error in event listener for "${event}":`, error)
}
})
}
removeAllListeners(event) {
if (event) {
this.events.delete(event)
} else {
this.events.clear()
}
}
listenerCount(event) {
return this.events.has(event) ? this.events.get(event).length : 0
}
eventNames() {
return Array.from(this.events.keys())
}
}
// 使用事件发射器
const emitter = new EventEmitter()
// 订阅事件
const unsubscribe = emitter.on('user:login', (user) => {
console.log(`User ${user.name} logged in`)
})
emitter.on('user:logout', (user) => {
console.log(`User ${user.name} logged out`)
})
// 一次性事件监听
emitter.once('app:ready', () => {
console.log('Application is ready!')
})
// 发射事件
emitter.emit('user:login', { name: 'John', id: 1 })
emitter.emit('app:ready')
emitter.emit('app:ready') // 不会触发,因为是once
// 取消订阅
unsubscribe()
// 状态管理器使用观察者模式
class StateManager extends EventEmitter {
constructor(initialState = {}) {
super()
this.state = { ...initialState }
this.history = [this.state]
this.currentIndex = 0
}
getState() {
return { ...this.state }
}
setState(updates) {
const prevState = { ...this.state }
this.state = { ...this.state, ...updates }
// 添加到历史记录
this.history = this.history.slice(0, this.currentIndex + 1)
this.history.push({ ...this.state })
this.currentIndex = this.history.length - 1
// 发射状态变化事件
this.emit('stateChange', {
prevState,
nextState: { ...this.state },
updates
})
}
subscribe(listener) {
return this.on('stateChange', listener)
}
undo() {
if (this.currentIndex > 0) {
this.currentIndex--
this.state = { ...this.history[this.currentIndex] }
this.emit('stateChange', {
prevState: this.history[this.currentIndex + 1],
nextState: { ...this.state },
type: 'undo'
})
}
}
redo() {
if (this.currentIndex < this.history.length - 1) {
this.currentIndex++
this.state = { ...this.history[this.currentIndex] }
this.emit('stateChange', {
prevState: this.history[this.currentIndex - 1],
nextState: { ...this.state },
type: 'redo'
})
}
}
}
// 使用状态管理器
const store = new StateManager({ count: 0, user: null })
// 订阅状态变化
store.subscribe(({ prevState, nextState, updates }) => {
console.log('State changed:', { prevState, nextState, updates })
})
// 更新状态
store.setState({ count: 1 })
store.setState({ user: { name: 'John' } })
store.setState({ count: 2 })
// 撤销/重做
store.undo() // count: 1
store.undo() // count: 0, user: null
store.redo() // count: 1
策略模式 (Strategy Pattern)
// 支付策略
class PaymentStrategy {
pay(amount) {
throw new Error('pay method must be implemented')
}
}
class CreditCardPayment extends PaymentStrategy {
constructor(cardNumber, cvv, expiryDate) {
super()
this.cardNumber = cardNumber
this.cvv = cvv
this.expiryDate = expiryDate
}
pay(amount) {
console.log(`Paid $${amount} using Credit Card ending in ${this.cardNumber.slice(-4)}`)
return {
success: true,
transactionId: `cc_${Date.now()}`,
amount,
method: 'credit_card'
}
}
}
class PayPalPayment extends PaymentStrategy {
constructor(email) {
super()
this.email = email
}
pay(amount) {
console.log(`Paid $${amount} using PayPal account ${this.email}`)
return {
success: true,
transactionId: `pp_${Date.now()}`,
amount,
method: 'paypal'
}
}
}
class CryptoPayment extends PaymentStrategy {
constructor(walletAddress, currency) {
super()
this.walletAddress = walletAddress
this.currency = currency
}
pay(amount) {
console.log(`Paid $${amount} using ${this.currency} from wallet ${this.walletAddress}`)
return {
success: true,
transactionId: `crypto_${Date.now()}`,
amount,
method: 'cryptocurrency',
currency: this.currency
}
}
}
// 支付处理器
class PaymentProcessor {
constructor() {
this.strategy = null
}
setStrategy(strategy) {
this.strategy = strategy
}
processPayment(amount) {
if (!this.strategy) {
throw new Error('Payment strategy not set')
}
return this.strategy.pay(amount)
}
}
// 使用支付策略
const processor = new PaymentProcessor()
// 信用卡支付
processor.setStrategy(new CreditCardPayment('1234567890123456', '123', '12/25'))
processor.processPayment(100)
// PayPal支付
processor.setStrategy(new PayPalPayment('user@example.com'))
processor.processPayment(50)
// 加密货币支付
processor.setStrategy(new CryptoPayment('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', 'BTC'))
processor.processPayment(0.001)
// 排序策略
class SortStrategy {
sort(array) {
throw new Error('sort method must be implemented')
}
}
class BubbleSort extends SortStrategy {
sort(array) {
const arr = [...array]
const n = arr.length
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]
}
}
}
return arr
}
}
class QuickSort extends SortStrategy {
sort(array) {
if (array.length <= 1) return array
const pivot = array[Math.floor(array.length / 2)]
const left = array.filter(x => x < pivot)
const middle = array.filter(x => x === pivot)
const right = array.filter(x => x > pivot)
return [...this.sort(left), ...middle, ...this.sort(right)]
}
}
class MergeSort extends SortStrategy {
sort(array) {
if (array.length <= 1) return array
const middle = Math.floor(array.length / 2)
const left = array.slice(0, middle)
const right = array.slice(middle)
return this.merge(this.sort(left), this.sort(right))
}
merge(left, right) {
const result = []
let leftIndex = 0
let rightIndex = 0
while (leftIndex < left.length && rightIndex < right.length) {
if (left[leftIndex] < right[rightIndex]) {
result.push(left[leftIndex])
leftIndex++
} else {
result.push(right[rightIndex])
rightIndex++
}
}
return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex))
}
}
// 排序器
class Sorter {
constructor(strategy) {
this.strategy = strategy
}
setStrategy(strategy) {
this.strategy = strategy
}
sort(array) {
console.time('Sort Time')
const result = this.strategy.sort(array)
console.timeEnd('Sort Time')
return result
}
}
// 使用排序策略
const data = [64, 34, 25, 12, 22, 11, 90]
const sorter = new Sorter(new QuickSort())
console.log('Original:', data)
console.log('Sorted:', sorter.sort(data))
// 切换策略
sorter.setStrategy(new MergeSort())
console.log('Merge sorted:', sorter.sort(data))
// 验证策略
class ValidationStrategy {
validate(value) {
throw new Error('validate method must be implemented')
}
}
class EmailValidation extends ValidationStrategy {
validate(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return {
isValid: emailRegex.test(email),
message: emailRegex.test(email) ? 'Valid email' : 'Invalid email format'
}
}
}
class PasswordValidation extends ValidationStrategy {
constructor(minLength = 8) {
super()
this.minLength = minLength
}
validate(password) {
const hasMinLength = password.length >= this.minLength
const hasUpperCase = /[A-Z]/.test(password)
const hasLowerCase = /[a-z]/.test(password)
const hasNumbers = /\d/.test(password)
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password)
const isValid = hasMinLength && hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar
const messages = []
if (!hasMinLength) messages.push(`At least ${this.minLength} characters`)
if (!hasUpperCase) messages.push('At least one uppercase letter')
if (!hasLowerCase) messages.push('At least one lowercase letter')
if (!hasNumbers) messages.push('At least one number')
if (!hasSpecialChar) messages.push('At least one special character')
return {
isValid,
message: isValid ? 'Valid password' : `Password must contain: ${messages.join(', ')}`
}
}
}
// 表单验证器
class FormValidator {
constructor() {
this.strategies = new Map()
}
addField(fieldName, strategy) {
this.strategies.set(fieldName, strategy)
}
validate(formData) {
const results = {}
let isFormValid = true
for (const [fieldName, strategy] of this.strategies) {
const value = formData[fieldName]
const result = strategy.validate(value)
results[fieldName] = result
if (!result.isValid) {
isFormValid = false
}
}
return {
isValid: isFormValid,
fields: results
}
}
}
// 使用表单验证器
const validator = new FormValidator()
validator.addField('email', new EmailValidation())
validator.addField('password', new PasswordValidation(10))
const formData = {
email: 'user@example.com',
password: 'MyPassword123!'
}
const validationResult = validator.validate(formData)
console.log('Validation result:', validationResult)
总结
JavaScript设计模式的核心要点:
- 创建型模式:单例、工厂、建造者模式解决对象创建问题
- 行为型模式:观察者、策略模式解决对象间交互问题
- 结构型模式:适配器、装饰器模式解决对象组合问题
- 现代应用:结合ES6+特性和现代开发需求
- 实战价值:提高代码可维护性、可扩展性和可复用性
设计模式不是银弹,需要根据具体场景选择合适的模式,避免过度设计。重要的是理解模式背后的思想,灵活运用到实际开发中。