设计模式-前端实战
组合拳:Vue 响应式系统
js
// 观察者模式(依赖收集)+ 代理模式(Proxy)
// Vue 3 响应式原理简化实现
// 代理模式:拦截对象读写
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key) // 观察者:收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const old = target[key]
const result = Reflect.set(target, key, value, receiver)
if (old !== value) {
trigger(target, key) // 观察者:触发更新
}
return result
},
})
}
// 观察者模式:依赖管理
let activeEffect = null
const targetMap = new WeakMap()
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect) // 收集依赖
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
deps?.forEach(effect => effect()) // 通知更新
}
// 使用
const state = reactive({ count: 0 })
effect(() => console.log('count:', state.count))
// 输出: count: 0
state.count++ // 输出: count: 1// 观察者模式(依赖收集)+ 代理模式(Proxy)
// Vue 3 响应式原理简化实现
// 代理模式:拦截对象读写
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
track(target, key) // 观察者:收集依赖
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const old = target[key]
const result = Reflect.set(target, key, value, receiver)
if (old !== value) {
trigger(target, key) // 观察者:触发更新
}
return result
},
})
}
// 观察者模式:依赖管理
let activeEffect = null
const targetMap = new WeakMap()
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect) // 收集依赖
}
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const deps = depsMap.get(key)
deps?.forEach(effect => effect()) // 通知更新
}
// 使用
const state = reactive({ count: 0 })
effect(() => console.log('count:', state.count))
// 输出: count: 0
state.count++ // 输出: count: 1组合拳:前端请求缓存
js
// 代理模式 + 策略模式 + 单例模式
class RequestManager {
static #instance
#cache = new Map()
#pending = new Map()
constructor() {
if (RequestManager.#instance) return RequestManager.#instance
RequestManager.#instance = this
}
createProxy(baseURL) {
const target = { baseURL }
return new Proxy(target, {
get: (_, apiPath) => {
return async (params = {}, strategy = 'network-first') => {
const cacheKey = `${baseURL}${apiPath}?${JSON.stringify(params)}`
// 策略模式:不同缓存策略
const strategies = {
// 仅网络
'network-only': () => this.#fetch(apiPath, params),
// 缓存优先
'cache-first': async () => {
const cached = this.#cache.get(cacheKey)
if (cached && !isExpired(cached)) return cached.data
return this.#fetchAndCache(apiPath, params, cacheKey)
},
// 网络优先
'network-first': async () => {
try {
return await this.#fetchAndCache(apiPath, params, cacheKey)
} catch {
const cached = this.#cache.get(cacheKey)
if (cached) return cached.data
throw new Error('Network error and no cache')
}
},
}
return strategies[strategy]()
}
},
})
}
async #fetchAndCache(path, params, key) {
const data = await this.#fetch(path, params)
this.#cache.set(key, { data, timestamp: Date.now() })
return data
}
async #fetch(path, params) {
// 防重复请求
const key = `${path}${JSON.stringify(params)}`
if (this.#pending.has(key)) {
return this.#pending.get(key)
}
const promise = axios.get(path, { params }).then(r => r.data)
this.#pending.set(key, promise)
const result = await promise
this.#pending.delete(key)
return result
}
}
function isExpired(entry, ttl = 60000) {
return Date.now() - entry.timestamp > ttl
}
// 使用
const api = new RequestManager()
const proxy = api.createProxy('https://api.example.com')
const data = await proxy('/users', { page: 1 }, 'cache-first')// 代理模式 + 策略模式 + 单例模式
class RequestManager {
static #instance
#cache = new Map()
#pending = new Map()
constructor() {
if (RequestManager.#instance) return RequestManager.#instance
RequestManager.#instance = this
}
createProxy(baseURL) {
const target = { baseURL }
return new Proxy(target, {
get: (_, apiPath) => {
return async (params = {}, strategy = 'network-first') => {
const cacheKey = `${baseURL}${apiPath}?${JSON.stringify(params)}`
// 策略模式:不同缓存策略
const strategies = {
// 仅网络
'network-only': () => this.#fetch(apiPath, params),
// 缓存优先
'cache-first': async () => {
const cached = this.#cache.get(cacheKey)
if (cached && !isExpired(cached)) return cached.data
return this.#fetchAndCache(apiPath, params, cacheKey)
},
// 网络优先
'network-first': async () => {
try {
return await this.#fetchAndCache(apiPath, params, cacheKey)
} catch {
const cached = this.#cache.get(cacheKey)
if (cached) return cached.data
throw new Error('Network error and no cache')
}
},
}
return strategies[strategy]()
}
},
})
}
async #fetchAndCache(path, params, key) {
const data = await this.#fetch(path, params)
this.#cache.set(key, { data, timestamp: Date.now() })
return data
}
async #fetch(path, params) {
// 防重复请求
const key = `${path}${JSON.stringify(params)}`
if (this.#pending.has(key)) {
return this.#pending.get(key)
}
const promise = axios.get(path, { params }).then(r => r.data)
this.#pending.set(key, promise)
const result = await promise
this.#pending.delete(key)
return result
}
}
function isExpired(entry, ttl = 60000) {
return Date.now() - entry.timestamp > ttl
}
// 使用
const api = new RequestManager()
const proxy = api.createProxy('https://api.example.com')
const data = await proxy('/users', { page: 1 }, 'cache-first')组合拳:Redux 中间件
js
// 职责链模式
function createStore(reducer) {
let state = reducer(undefined, { type: '__INIT__' })
let listeners = []
const store = {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action)
listeners.forEach(l => l())
},
subscribe: (listener) => {
listeners.push(listener)
return () => {
listeners = listeners.filter(l => l !== listener)
}
},
}
return store
}
// 中间件函数
const logger = (store) => (next) => (action) => {
console.log('dispatching:', action)
const result = next(action)
console.log('next state:', store.getState())
return result
}
const thunk = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState)
}
return next(action)
}
// 应用中间件(职责链)
function applyMiddleware(store, middlewares) {
let dispatch = store.dispatch
middlewares.reverse().forEach(middleware => {
dispatch = middleware(store)(dispatch)
})
return { ...store, dispatch }
}// 职责链模式
function createStore(reducer) {
let state = reducer(undefined, { type: '__INIT__' })
let listeners = []
const store = {
getState: () => state,
dispatch: (action) => {
state = reducer(state, action)
listeners.forEach(l => l())
},
subscribe: (listener) => {
listeners.push(listener)
return () => {
listeners = listeners.filter(l => l !== listener)
}
},
}
return store
}
// 中间件函数
const logger = (store) => (next) => (action) => {
console.log('dispatching:', action)
const result = next(action)
console.log('next state:', store.getState())
return result
}
const thunk = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState)
}
return next(action)
}
// 应用中间件(职责链)
function applyMiddleware(store, middlewares) {
let dispatch = store.dispatch
middlewares.reverse().forEach(middleware => {
dispatch = middleware(store)(dispatch)
})
return { ...store, dispatch }
}组合拳:组件配置系统
js
// 建造者模式 + 外观模式
class ComponentBuilder {
constructor(tag) {
this.element = document.createElement(tag)
}
text(content) {
this.element.textContent = content
return this
}
class(name) {
this.element.classList.add(name)
return this
}
on(event, handler) {
this.element.addEventListener(event, handler)
return this
}
style(styles) {
Object.assign(this.element.style, styles)
return this
}
attr(name, value) {
this.element.setAttribute(name, value)
return this
}
append(child) {
this.element.appendChild(child instanceof ComponentBuilder ? child.build() : child)
return this
}
build() {
return this.element
}
}
// 外观模式封装
const UI = {
button(text, onClick) {
return new ComponentBuilder('button')
.class('btn')
.class('btn-primary')
.text(text)
.on('click', onClick)
.build()
},
card(title, content) {
return new ComponentBuilder('div')
.class('card')
.append(
new ComponentBuilder('div')
.class('card-title')
.text(title)
.build()
)
.append(
new ComponentBuilder('div')
.class('card-body')
.text(content)
.build()
)
.build()
},
modal(config) {
const overlay = new ComponentBuilder('div').class('modal-overlay').build()
const modal = new ComponentBuilder('div')
.class('modal')
.append(
new ComponentBuilder('div').class('modal-header').text(config.title).build()
)
.append(
new ComponentBuilder('div').class('modal-body').text(config.content).build()
)
.build()
overlay.appendChild(modal)
return overlay
},
}
// 使用
const btn = UI.button('Click me', () => alert('Hello'))
document.body.appendChild(btn)
const card = UI.card('Title', 'Content')
document.body.appendChild(card)// 建造者模式 + 外观模式
class ComponentBuilder {
constructor(tag) {
this.element = document.createElement(tag)
}
text(content) {
this.element.textContent = content
return this
}
class(name) {
this.element.classList.add(name)
return this
}
on(event, handler) {
this.element.addEventListener(event, handler)
return this
}
style(styles) {
Object.assign(this.element.style, styles)
return this
}
attr(name, value) {
this.element.setAttribute(name, value)
return this
}
append(child) {
this.element.appendChild(child instanceof ComponentBuilder ? child.build() : child)
return this
}
build() {
return this.element
}
}
// 外观模式封装
const UI = {
button(text, onClick) {
return new ComponentBuilder('button')
.class('btn')
.class('btn-primary')
.text(text)
.on('click', onClick)
.build()
},
card(title, content) {
return new ComponentBuilder('div')
.class('card')
.append(
new ComponentBuilder('div')
.class('card-title')
.text(title)
.build()
)
.append(
new ComponentBuilder('div')
.class('card-body')
.text(content)
.build()
)
.build()
},
modal(config) {
const overlay = new ComponentBuilder('div').class('modal-overlay').build()
const modal = new ComponentBuilder('div')
.class('modal')
.append(
new ComponentBuilder('div').class('modal-header').text(config.title).build()
)
.append(
new ComponentBuilder('div').class('modal-body').text(config.content).build()
)
.build()
overlay.appendChild(modal)
return overlay
},
}
// 使用
const btn = UI.button('Click me', () => alert('Hello'))
document.body.appendChild(btn)
const card = UI.card('Title', 'Content')
document.body.appendChild(card)
Cao Note