Skip to content

设计模式-前端实战 ​

组合拳: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)