行为型模式
观察者模式
js
// 响应式数据(Vue 2 核心)
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => effect())
}
}
// 自定义事件
class EventEmitter {
constructor() {
this.events = {}
}
on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
return () => this.off(event, callback)
}
emit(event, ...args) {
this.events[event]?.forEach(cb => cb(...args))
}
off(event, callback) {
this.events[event] = this.events[event]?.filter(cb => cb !== callback)
}
}// 响应式数据(Vue 2 核心)
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => effect())
}
}
// 自定义事件
class EventEmitter {
constructor() {
this.events = {}
}
on(event, callback) {
if (!this.events[event]) this.events[event] = []
this.events[event].push(callback)
return () => this.off(event, callback)
}
emit(event, ...args) {
this.events[event]?.forEach(cb => cb(...args))
}
off(event, callback) {
this.events[event] = this.events[event]?.filter(cb => cb !== callback)
}
}发布订阅模式
js
// 跨组件通信
const eventBus = new EventEmitter()
// 组件 A(发布)
eventBus.emit('user:login', { id: 1, name: 'Alice' })
// 组件 B(订阅)
useEffect(() => {
const unsubscribe = eventBus.on('user:login', (user) => {
console.log('用户登录:', user)
})
return unsubscribe
}, [])
// 更完善的 EventBus
class EventBus {
#listeners = new Map()
on(event, handler) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, new Set())
}
this.#listeners.get(event).add(handler)
return () => this.off(event, handler)
}
emit(event, payload) {
this.#listeners.get(event)?.forEach(handler => {
try { handler(payload) } catch (e) { console.error(e) }
})
}
off(event, handler) {
this.#listeners.get(event)?.delete(handler)
}
clear() {
this.#listeners.clear()
}
}// 跨组件通信
const eventBus = new EventEmitter()
// 组件 A(发布)
eventBus.emit('user:login', { id: 1, name: 'Alice' })
// 组件 B(订阅)
useEffect(() => {
const unsubscribe = eventBus.on('user:login', (user) => {
console.log('用户登录:', user)
})
return unsubscribe
}, [])
// 更完善的 EventBus
class EventBus {
#listeners = new Map()
on(event, handler) {
if (!this.#listeners.has(event)) {
this.#listeners.set(event, new Set())
}
this.#listeners.get(event).add(handler)
return () => this.off(event, handler)
}
emit(event, payload) {
this.#listeners.get(event)?.forEach(handler => {
try { handler(payload) } catch (e) { console.error(e) }
})
}
off(event, handler) {
this.#listeners.get(event)?.delete(handler)
}
clear() {
this.#listeners.clear()
}
}策略模式
js
// 表单校验
const validators = {
required: (value) => value ? '' : '此项必填',
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? '' : '邮箱格式不正确',
minLength: (min) => (value) =>
value.length >= min ? '' : `至少需要 ${min} 个字符`,
maxLength: (max) => (value) =>
value.length <= max ? '' : `最多 ${max} 个字符`,
phone: (value) => /^1[3-9]\d{9}$/.test(value) ? '' : '手机号格式不正确',
}
function validate(value, rules) {
for (const rule of rules) {
const validator = typeof rule === 'function'
? rule
: validators[rule.name](rule.params)
const error = validator(value)
if (error) return error
}
return ''
}
// 使用
validate('test', [
{ name: 'required' },
{ name: 'minLength', params: 6 },
])
// 动画策略
const animations = {
fade: {
enter: (el) => { el.style.opacity = 0; requestAnimationFrame(() => { el.style.opacity = 1 }) },
leave: (el) => { el.style.opacity = 0 },
},
slide: {
enter: (el) => { el.style.transform = 'translateX(-100%)'; requestAnimationFrame(() => { el.style.transform = 'translateX(0)' }) },
leave: (el) => { el.style.transform = 'translateX(100%)' },
},
scale: {
enter: (el) => { el.style.transform = 'scale(0)'; requestAnimationFrame(() => { el.style.transform = 'scale(1)' }) },
leave: (el) => { el.style.transform = 'scale(0)' },
},
}
function useAnimation(type, ref) {
const strategy = animations[type]
return {
enter: () => strategy.enter(ref.value),
leave: () => strategy.leave(ref.value),
}
}// 表单校验
const validators = {
required: (value) => value ? '' : '此项必填',
email: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? '' : '邮箱格式不正确',
minLength: (min) => (value) =>
value.length >= min ? '' : `至少需要 ${min} 个字符`,
maxLength: (max) => (value) =>
value.length <= max ? '' : `最多 ${max} 个字符`,
phone: (value) => /^1[3-9]\d{9}$/.test(value) ? '' : '手机号格式不正确',
}
function validate(value, rules) {
for (const rule of rules) {
const validator = typeof rule === 'function'
? rule
: validators[rule.name](rule.params)
const error = validator(value)
if (error) return error
}
return ''
}
// 使用
validate('test', [
{ name: 'required' },
{ name: 'minLength', params: 6 },
])
// 动画策略
const animations = {
fade: {
enter: (el) => { el.style.opacity = 0; requestAnimationFrame(() => { el.style.opacity = 1 }) },
leave: (el) => { el.style.opacity = 0 },
},
slide: {
enter: (el) => { el.style.transform = 'translateX(-100%)'; requestAnimationFrame(() => { el.style.transform = 'translateX(0)' }) },
leave: (el) => { el.style.transform = 'translateX(100%)' },
},
scale: {
enter: (el) => { el.style.transform = 'scale(0)'; requestAnimationFrame(() => { el.style.transform = 'scale(1)' }) },
leave: (el) => { el.style.transform = 'scale(0)' },
},
}
function useAnimation(type, ref) {
const strategy = animations[type]
return {
enter: () => strategy.enter(ref.value),
leave: () => strategy.leave(ref.value),
}
}状态模式
js
// 页面状态:loading → empty → error → success
class PageState {
constructor(context) {
this.context = context
}
render() { throw new Error('子类必须实现 render 方法') }
}
class LoadingState extends PageState {
render() {
return `<div class="loading"><Spinner /></div>`
}
}
class EmptyState extends PageState {
render() {
return `<div class="empty"><EmptyPlaceholder @retry="${this.context.fetch}" /></div>`
}
}
class ErrorState extends PageState {
render() {
return `<div class="error"><ErrorAlert :message="${this.context.error}" @retry="${this.context.fetch}" /></div>`
}
}
class DataState extends PageState {
render() {
return `<div class="data"><DataTable :data="${this.context.data}" /></div>`
}
}
class PageContext {
constructor() {
this.states = {
loading: new LoadingState(this),
empty: new EmptyState(this),
error: new ErrorState(this),
data: new DataState(this),
}
this.currentState = this.states.loading
}
setState(name) {
this.currentState = this.states[name]
this.render()
}
render() {
return this.currentState.render()
}
}// 页面状态:loading → empty → error → success
class PageState {
constructor(context) {
this.context = context
}
render() { throw new Error('子类必须实现 render 方法') }
}
class LoadingState extends PageState {
render() {
return `<div class="loading"><Spinner /></div>`
}
}
class EmptyState extends PageState {
render() {
return `<div class="empty"><EmptyPlaceholder @retry="${this.context.fetch}" /></div>`
}
}
class ErrorState extends PageState {
render() {
return `<div class="error"><ErrorAlert :message="${this.context.error}" @retry="${this.context.fetch}" /></div>`
}
}
class DataState extends PageState {
render() {
return `<div class="data"><DataTable :data="${this.context.data}" /></div>`
}
}
class PageContext {
constructor() {
this.states = {
loading: new LoadingState(this),
empty: new EmptyState(this),
error: new ErrorState(this),
data: new DataState(this),
}
this.currentState = this.states.loading
}
setState(name) {
this.currentState = this.states[name]
this.render()
}
render() {
return this.currentState.render()
}
}职责链模式
js
// 中间件模式
class MiddlewareChain {
constructor() {
this.middlewares = []
}
use(fn) {
this.middlewares.push(fn)
return this
}
async execute(context) {
async function dispatch(i) {
if (i >= chain.middlewares.length) return
const fn = chain.middlewares[i]
await fn(context, () => dispatch(i + 1))
}
const chain = this
await dispatch(0)
}
}
// 使用
const pipeline = new MiddlewareChain()
// 日志中间件
pipeline.use(async (ctx, next) => {
console.time(ctx.url)
await next()
console.timeEnd(ctx.url)
})
// 认证中间件
pipeline.use(async (ctx, next) => {
if (!ctx.token) throw new Error('Unauthorized')
ctx.user = await verifyToken(ctx.token)
await next()
})
// 缓存中间件
pipeline.use(async (ctx, next) => {
const cached = cache.get(ctx.url)
if (cached) {
ctx.result = cached
return
}
await next()
cache.set(ctx.url, ctx.result)
})
// Vue Router 导航守卫就是职责链
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/login')
} else {
next()
}
})// 中间件模式
class MiddlewareChain {
constructor() {
this.middlewares = []
}
use(fn) {
this.middlewares.push(fn)
return this
}
async execute(context) {
async function dispatch(i) {
if (i >= chain.middlewares.length) return
const fn = chain.middlewares[i]
await fn(context, () => dispatch(i + 1))
}
const chain = this
await dispatch(0)
}
}
// 使用
const pipeline = new MiddlewareChain()
// 日志中间件
pipeline.use(async (ctx, next) => {
console.time(ctx.url)
await next()
console.timeEnd(ctx.url)
})
// 认证中间件
pipeline.use(async (ctx, next) => {
if (!ctx.token) throw new Error('Unauthorized')
ctx.user = await verifyToken(ctx.token)
await next()
})
// 缓存中间件
pipeline.use(async (ctx, next) => {
const cached = cache.get(ctx.url)
if (cached) {
ctx.result = cached
return
}
await next()
cache.set(ctx.url, ctx.result)
})
// Vue Router 导航守卫就是职责链
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next('/login')
} else {
next()
}
})迭代器模式
js
// JavaScript 内置迭代器
const iterable = {
*[Symbol.iterator]() {
yield 'a'
yield 'b'
yield 'c'
}
}
// 分页迭代器
function createPaginator(fetchPage) {
let page = 1
let hasMore = true
return {
[Symbol.asyncIterator]() {
return {
async next() {
if (!hasMore) return { done: true }
const data = await fetchPage(page++)
hasMore = data.hasMore
return { value: data.items, done: !data.items.length }
},
}
},
}
}
// 使用
const paginator = createPaginator((page) =>
fetch(`/api/items?page=${page}`).then(r => r.json())
)
for await (const items of paginator) {
renderItems(items)
}// JavaScript 内置迭代器
const iterable = {
*[Symbol.iterator]() {
yield 'a'
yield 'b'
yield 'c'
}
}
// 分页迭代器
function createPaginator(fetchPage) {
let page = 1
let hasMore = true
return {
[Symbol.asyncIterator]() {
return {
async next() {
if (!hasMore) return { done: true }
const data = await fetchPage(page++)
hasMore = data.hasMore
return { value: data.items, done: !data.items.length }
},
}
},
}
}
// 使用
const paginator = createPaginator((page) =>
fetch(`/api/items?page=${page}`).then(r => r.json())
)
for await (const items of paginator) {
renderItems(items)
}
Cao Note