Skip to content

行为型模式 ​

观察者模式 ​

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)
}