Skip to content

设计模式

观察者模式(Observer)

概念

观察者模式定义了对象间一对多的依赖关系:当一个对象(Subject,被观察者)的状态发生变化时,所有依赖它的对象(Observer,观察者)都会被自动通知并更新。

UML 关系

┌─────────────────────┐         ┌──────────────────────┐
│      Subject        │         │      Observer         │
├─────────────────────┤         ├──────────────────────┤
│ - observers: []     │ 1    *  │                      │
│                     │────────▶│ + update(data): void │
│ + attach(observer)  │         │                      │
│ + detach(observer)  │         └──────────────────────┘
│ + notify()          │                    ▲
└─────────────────────┘                    │
                                ┌──────────┴───────────┐
                                │  ConcreteObserver     │
                                ├──────────────────────┤
                                │ + update(data): void │
                                └──────────────────────┘

Subject 直接持有 Observer 的引用列表,状态变化时遍历列表调用每个 Observer 的 update 方法。观察者和被观察者之间是直接依赖关系。

代码实现

js
class Subject {
  constructor() {
    this.observers = []
  }

  attach(observer) {
    if (!this.observers.includes(observer)) {
      this.observers.push(observer)
    }
  }

  detach(observer) {
    this.observers = this.observers.filter(o => o !== observer)
  }

  notify(data) {
    this.observers.forEach(observer => observer.update(data))
  }
}

class Observer {
  constructor(name) {
    this.name = name
  }

  update(data) {
    console.log(`${this.name} received: ${JSON.stringify(data)}`)
  }
}

const subject = new Subject()
const obs1 = new Observer('Observer-1')
const obs2 = new Observer('Observer-2')

subject.attach(obs1)
subject.attach(obs2)
subject.notify({ type: 'update', value: 42 })
subject.detach(obs1)
subject.notify({ type: 'update', value: 100 })

与发布订阅模式的区别

维度观察者模式发布订阅模式
耦合度主题与观察者直接耦合发布者与订阅者完全解耦
中间层有事件中心(Broker/EventBus)
通信方式主题直接调用观察者方法通过事件通道间接通信
事件类型通常为单一事件支持多种命名事件
典型实现DOM EventTargetNode.js EventEmitter

核心区别在于:观察者模式是面对面通信,发布订阅模式有一个中间调度中心。观察者模式中 Subject 需要知道 Observer 的存在(持有引用),而发布订阅模式中发布者和订阅者互不知晓,只通过事件名关联。

前端实际应用

  • MutationObserver:观察 DOM 变化
  • IntersectionObserver:观察元素可见性
  • ResizeObserver:观察元素尺寸变化
  • Vue 2 响应式系统的 Dep(Subject)与 Watcher(Observer)
  • RxJS 的 Observable/Observer

发布订阅模式(Pub/Sub)

概念

发布订阅模式在观察者模式的基础上引入了一个事件中心(Event Channel),发布者通过事件中心发布消息,订阅者通过事件中心订阅消息。发布者和订阅者无需互相知晓,实现了完全解耦。

UML 关系

┌───────────┐     ┌──────────────────────┐     ┌────────────┐
│ Publisher  │────▶│    EventEmitter      │◀────│ Subscriber │
│           │     ├──────────────────────┤     │            │
│ emit(event│     │ - events: Map        │     │ on(event,  │
│   , data) │     │                      │     │   callback)│
└───────────┘     │ + on(event, fn)      │     └────────────┘
                  │ + off(event, fn)     │
                  │ + once(event, fn)    │
                  │ + emit(event, ...args│
                  └──────────────────────┘

手写 EventEmitter 完整实现

js
class EventEmitter {
  constructor() {
    this._events = new Map()
  }

  on(event, fn) {
    const fns = this._events.get(event) || []
    fns.push(fn)
    this._events.set(event, fns)
    return this
  }

  off(event, fn) {
    if (!fn) {
      this._events.delete(event)
      return this
    }
    const fns = this._events.get(event)
    if (!fns) return this
    this._events.set(
      event,
      fns.filter(f => f !== fn && f._original !== fn)
    )
    return this
  }

  once(event, fn) {
    const wrapper = (...args) => {
      fn.apply(this, args)
      this.off(event, wrapper)
    }
    wrapper._original = fn
    this.on(event, wrapper)
    return this
  }

  emit(event, ...args) {
    const fns = this._events.get(event)
    if (!fns || fns.length === 0) return false
    fns.slice().forEach(fn => fn.apply(this, args))
    return true
  }

  listenerCount(event) {
    const fns = this._events.get(event)
    return fns ? fns.length : 0
  }

  removeAllListeners(event) {
    if (event) {
      this._events.delete(event)
    } else {
      this._events.clear()
    }
    return this
  }
}

实现细节要点:

  • once 通过包装函数实现,执行后自动解绑。wrapper._original = fn 是为了让 off 在取消 once 注册的事件时能正确匹配原始函数
  • emit 中使用 fns.slice() 创建副本后遍历,避免在回调中调用 off 导致数组在遍历时被修改
  • 链式调用:on/off/once 均返回 this

前端实际应用

  • Node.js events 模块
  • Vue 2 的 $on/$off/$emit(组件事件系统)
  • 微前端通信(qiankun initGlobalState
  • WebSocket 消息分发
  • 跨组件通信 EventBus

策略模式(Strategy)

概念

策略模式定义一系列算法,将每个算法封装为独立策略对象,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户端。

UML 关系

┌──────────────┐       ┌─────────────────────┐
│   Context    │       │    Strategy          │
├──────────────┤       ├─────────────────────┤
│ - strategy   │──────▶│ + execute(): result │
│              │       └─────────────────────┘
│ + setStrategy│                 ▲
│ + execute()  │       ┌────────┼────────┐
└──────────────┘       │        │        │
                  StrategyA  StrategyB  StrategyC

代码实现:表单验证场景

js
const validators = {
  required(value) {
    return value !== '' && value !== null && value !== undefined
      ? { valid: true }
      : { valid: false, message: '此字段为必填项' }
  },

  minLength(value, min) {
    return value.length >= min
      ? { valid: true }
      : { valid: false, message: `最少需要 ${min} 个字符` }
  },

  maxLength(value, max) {
    return value.length <= max
      ? { valid: true }
      : { valid: false, message: `最多允许 ${max} 个字符` }
  },

  pattern(value, regex) {
    return regex.test(value)
      ? { valid: true }
      : { valid: false, message: '格式不正确' }
  },

  email(value) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
      ? { valid: true }
      : { valid: false, message: '请输入有效的邮箱地址' }
  },

  phone(value) {
    return /^1[3-9]\d{9}$/.test(value)
      ? { valid: true }
      : { valid: false, message: '请输入有效的手机号码' }
  }
}

class FormValidator {
  constructor() {
    this.rules = new Map()
  }

  addRule(field, ruleConfigs) {
    this.rules.set(field, ruleConfigs)
    return this
  }

  validate(formData) {
    const errors = {}
    let isValid = true

    for (const [field, ruleConfigs] of this.rules) {
      const value = formData[field]

      for (const config of ruleConfigs) {
        const { strategy, ...params } = config
        const validator = validators[strategy]
        const args = [value, ...Object.values(params)]
        const result = validator(...args)

        if (!result.valid) {
          errors[field] = config.message || result.message
          isValid = false
          break
        }
      }
    }

    return { isValid, errors }
  }
}

const validator = new FormValidator()
validator
  .addRule('username', [
    { strategy: 'required' },
    { strategy: 'minLength', min: 3 },
    { strategy: 'maxLength', max: 20 }
  ])
  .addRule('email', [
    { strategy: 'required' },
    { strategy: 'email' }
  ])
  .addRule('phone', [
    { strategy: 'required' },
    { strategy: 'phone' }
  ])

const result = validator.validate({
  username: 'ab',
  email: 'invalid',
  phone: '12345'
})

前端实际应用

  • 表单验证策略(如 async-validator、VeeValidate)
  • 权限控制策略(不同角色不同操作集)
  • 动画缓动函数(linear、ease-in、ease-out 作为策略)
  • 数据格式化(日期、货币、数字的多种格式化策略)
  • 日志上报策略(不同环境使用不同上报方式)

工厂模式(Factory)

概念

工厂模式将对象的创建逻辑封装起来,调用方无需知道具体的创建细节。根据抽象程度的不同,分为三种变体。

简单工厂

由一个工厂函数根据参数创建不同类型的对象:

js
class Button {
  constructor(options) {
    this.type = options.type
    this.text = options.text
    this.size = options.size || 'medium'
  }

  render() {
    return `<button class="btn-${this.type} btn-${this.size}">${this.text}</button>`
  }
}

class Input {
  constructor(options) {
    this.type = options.inputType || 'text'
    this.placeholder = options.placeholder
  }

  render() {
    return `<input type="${this.type}" placeholder="${this.placeholder}" />`
  }
}

class Select {
  constructor(options) {
    this.options = options.options || []
    this.placeholder = options.placeholder
  }

  render() {
    const opts = this.options.map(o => `<option value="${o.value}">${o.label}</option>`).join('')
    return `<select>${opts}</select>`
  }
}

function createComponent(type, options) {
  const componentMap = {
    button: Button,
    input: Input,
    select: Select
  }

  const Component = componentMap[type]
  if (!Component) {
    throw new Error(`Unknown component type: ${type}`)
  }

  return new Component(options)
}

const btn = createComponent('button', { type: 'primary', text: '提交' })
const input = createComponent('input', { placeholder: '请输入' })

工厂方法

将创建对象的职责委托给子类,每个子类负责创建特定类型的产品:

js
class DialogFactory {
  createDialog() {
    throw new Error('Subclass must implement createDialog')
  }

  show(options) {
    const dialog = this.createDialog()
    dialog.setContent(options.content)
    dialog.render()
    return dialog
  }
}

class ConfirmDialogFactory extends DialogFactory {
  createDialog() {
    return new ConfirmDialog()
  }
}

class AlertDialogFactory extends DialogFactory {
  createDialog() {
    return new AlertDialog()
  }
}

class ConfirmDialog {
  setContent(content) {
    this.content = content
  }

  render() {
    console.log(`[Confirm] ${this.content} [OK] [Cancel]`)
  }
}

class AlertDialog {
  setContent(content) {
    this.content = content
  }

  render() {
    console.log(`[Alert] ${this.content} [OK]`)
  }
}

const confirmFactory = new ConfirmDialogFactory()
confirmFactory.show({ content: '确定要删除吗?' })

抽象工厂

提供一个创建一系列相关对象的接口,确保产品族之间的一致性:

js
class LightThemeFactory {
  createButton(text) {
    return {
      render: () => `<button class="light-btn">${text}</button>`,
      theme: 'light'
    }
  }

  createCard(content) {
    return {
      render: () => `<div class="light-card">${content}</div>`,
      theme: 'light'
    }
  }

  createInput(placeholder) {
    return {
      render: () => `<input class="light-input" placeholder="${placeholder}" />`,
      theme: 'light'
    }
  }
}

class DarkThemeFactory {
  createButton(text) {
    return {
      render: () => `<button class="dark-btn">${text}</button>`,
      theme: 'dark'
    }
  }

  createCard(content) {
    return {
      render: () => `<div class="dark-card">${content}</div>`,
      theme: 'dark'
    }
  }

  createInput(placeholder) {
    return {
      render: () => `<input class="dark-input" placeholder="${placeholder}" />`,
      theme: 'dark'
    }
  }
}

function createUI(factory) {
  const button = factory.createButton('Submit')
  const card = factory.createCard('Hello World')
  const input = factory.createInput('Type here...')
  return { button, card, input }
}

const lightUI = createUI(new LightThemeFactory())
const darkUI = createUI(new DarkThemeFactory())

UML 关系(抽象工厂)

┌─────────────────────┐
│  AbstractFactory     │
├─────────────────────┤
│ + createButton()    │
│ + createCard()      │
│ + createInput()     │
└─────────┬───────────┘

    ┌─────┴──────┐
    │            │
┌───┴────┐  ┌───┴────┐
│ Light  │  │ Dark   │     ← 每个工厂生产同一主题的完整产品族
│ Factory│  │ Factory│
└────────┘  └────────┘

前端实际应用

  • React createElement 就是一个简单工厂
  • 组件库的主题系统(Ant Design 的 ConfigProvider)
  • 跨平台 UI 抽象(React Native、Taro 根据平台产出不同组件)
  • 日志系统(根据环境创建不同的 Logger 实例)

单例模式(Singleton)

概念

单例模式确保一个类只有一个实例,并提供一个全局访问点。在前端中常用于管理全局状态、弹窗、缓存等共享资源。

UML 关系

┌────────────────────────┐
│      Singleton          │
├────────────────────────┤
│ - static instance      │
├────────────────────────┤
│ - constructor()        │  ← 私有构造
│ + static getInstance() │  ← 全局访问点
│ + operation()          │
└────────────────────────┘

懒汉式实现

实例在首次访问时才创建:

js
class Singleton {
  static instance = null

  static getInstance(...args) {
    if (!Singleton.instance) {
      Singleton.instance = new Singleton(...args)
    }
    return Singleton.instance
  }

  constructor(config) {
    if (Singleton.instance) {
      return Singleton.instance
    }
    this.config = config
    Singleton.instance = this
  }
}

const a = Singleton.getInstance({ theme: 'dark' })
const b = Singleton.getInstance({ theme: 'light' })
a === b
a.config.theme

代理实现

利用闭包和代理分离单例管理逻辑与业务逻辑,实现更通用的单例方案:

js
function createSingleton(ClassRef) {
  let instance = null
  return new Proxy(ClassRef, {
    construct(target, args) {
      if (!instance) {
        instance = Reflect.construct(target, args)
      }
      return instance
    }
  })
}

class Store {
  constructor(initialState) {
    this.state = initialState
  }

  getState() {
    return this.state
  }

  setState(partial) {
    this.state = { ...this.state, ...partial }
  }
}

const SingletonStore = createSingleton(Store)

const store1 = new SingletonStore({ count: 0 })
const store2 = new SingletonStore({ count: 100 })
store1 === store2
store1.getState()

也可以用高阶函数实现更函数式的版本:

js
function singleton(fn) {
  let result
  return function (...args) {
    if (!result) {
      result = fn.apply(this, args)
    }
    return result
  }
}

const createStore = singleton((initialState) => {
  let state = initialState

  return {
    getState: () => state,
    setState: (partial) => { state = { ...state, ...partial } }
  }
})

应用场景

全局模态框管理

js
class ModalManager {
  static instance = null

  static getInstance() {
    if (!ModalManager.instance) {
      ModalManager.instance = new ModalManager()
    }
    return ModalManager.instance
  }

  constructor() {
    this.modals = new Map()
    this.activeModal = null
  }

  register(id, modal) {
    this.modals.set(id, modal)
  }

  open(id, props) {
    if (this.activeModal) {
      this.close(this.activeModal)
    }
    const modal = this.modals.get(id)
    if (modal) {
      modal.open(props)
      this.activeModal = id
    }
  }

  close(id) {
    const modal = this.modals.get(id || this.activeModal)
    if (modal) {
      modal.close()
      this.activeModal = null
    }
  }
}

前端实际应用

  • Vuex/Pinia/Redux Store(全局唯一状态树)
  • 全局 Loading/Toast 管理器
  • WebSocket 连接管理
  • 浏览器 window 对象本身就是单例
  • 日志收集器(全局唯一 Logger 实例)

代理模式(Proxy)

概念

代理模式为目标对象提供一个代理对象,由代理对象控制对目标对象的访问。代理可以在不改变目标对象接口的前提下,对访问进行拦截和增强。

UML 关系

┌──────────────┐       ┌──────────────────────┐
│   Client     │──────▶│     Subject          │
└──────────────┘       ├──────────────────────┤
                       │ + request()          │
                       └──────────┬───────────┘

                      ┌───────────┴──────────┐
                      │                      │
               ┌──────┴──────┐      ┌────────┴────────┐
               │   Proxy     │─────▶│  RealSubject    │
               ├─────────────┤      ├─────────────────┤
               │ + request() │      │ + request()     │
               └─────────────┘      └─────────────────┘

与 ES6 Proxy 的关系

ES6 Proxy 是语言层面对代理模式的原生实现。它提供了一种机制,可以拦截并自定义对象的基本操作(属性读取、赋值、枚举、函数调用等):

js
const handler = {
  get(target, prop, receiver) {
    console.log(`Reading property: ${String(prop)}`)
    return Reflect.get(target, prop, receiver)
  },

  set(target, prop, value, receiver) {
    console.log(`Setting ${String(prop)} = ${value}`)
    return Reflect.set(target, prop, value, receiver)
  },

  deleteProperty(target, prop) {
    console.log(`Deleting property: ${String(prop)}`)
    return Reflect.deleteProperty(target, prop)
  }
}

const obj = new Proxy({}, handler)
obj.name = 'Alice'
obj.name
delete obj.name

Vue 3 的响应式系统正是基于 Proxy 实现的,通过拦截 get 收集依赖、拦截 set 触发更新。

缓存代理

js
function createCacheProxy(fn) {
  const cache = new Map()

  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args)

      if (cache.has(key)) {
        console.log(`Cache hit for: ${key}`)
        return cache.get(key)
      }

      const result = Reflect.apply(target, thisArg, args)

      if (result instanceof Promise) {
        return result.then(value => {
          cache.set(key, value)
          return value
        })
      }

      cache.set(key, result)
      return result
    }
  })
}

function fibonacci(n) {
  if (n <= 1) return n
  return fibonacci(n - 1) + fibonacci(n - 2)
}

const cachedFib = createCacheProxy(fibonacci)
cachedFib(40)
cachedFib(40)

const fetchUser = createCacheProxy(async (id) => {
  const res = await fetch(`/api/users/${id}`)
  return res.json()
})

虚拟代理

延迟创建开销大的对象,直到真正需要时才初始化:

js
class HeavyEditor {
  constructor() {
    this.loaded = false
  }

  async init() {
    console.log('Loading editor resources...')
    await new Promise(resolve => setTimeout(resolve, 2000))
    this.loaded = true
    console.log('Editor ready')
  }

  setContent(content) {
    this.content = content
  }

  getContent() {
    return this.content
  }
}

class EditorProxy {
  constructor() {
    this.editor = null
    this.pendingOps = []
  }

  async ensureEditor() {
    if (!this.editor) {
      this.editor = new HeavyEditor()
      await this.editor.init()

      for (const op of this.pendingOps) {
        op(this.editor)
      }
      this.pendingOps = []
    }
    return this.editor
  }

  setContent(content) {
    if (this.editor) {
      this.editor.setContent(content)
    } else {
      this.pendingOps.push(editor => editor.setContent(content))
    }
  }

  async getContent() {
    const editor = await this.ensureEditor()
    return editor.getContent()
  }
}

const editor = new EditorProxy()
editor.setContent('Hello World')
editor.getContent().then(console.log)

前端实际应用

  • Vue 3 reactive/ref 响应式代理
  • 图片懒加载(虚拟代理:先加载占位图,可视时加载真实图片)
  • API 请求缓存代理
  • 属性验证代理(限制对象属性的类型和范围)
  • Proxy 实现不可变数据(拦截 set 抛出错误)

装饰器模式(Decorator)

概念

装饰器模式在不修改原始对象的前提下,动态地为对象添加额外的职责。与继承相比,装饰器更加灵活——可以在运行时组合多个装饰器,形成功能叠加。

UML 关系

┌──────────────────┐
│    Component     │
├──────────────────┤
│ + operation()    │
└────────┬─────────┘

    ┌────┴──────────────┐
    │                   │
┌───┴──────────┐  ┌─────┴──────────┐
│ Concrete     │  │   Decorator    │
│ Component    │  ├────────────────┤
│              │  │ - component    │──▶ Component
└──────────────┘  │ + operation()  │
                  └────────┬───────┘

                  ┌────────┴───────┐
                  │ConcreteDecorator│
                  │ + operation()  │
                  └────────────────┘

AOP(面向切面编程)实现

js
Function.prototype.before = function (beforeFn) {
  const self = this
  return function (...args) {
    beforeFn.apply(this, args)
    return self.apply(this, args)
  }
}

Function.prototype.after = function (afterFn) {
  const self = this
  return function (...args) {
    const result = self.apply(this, args)
    afterFn.apply(this, args)
    return result
  }
}

function submitForm(data) {
  console.log('Submitting:', data)
  return { success: true }
}

const enhanced = submitForm
  .before((data) => {
    console.log('Validating:', data)
  })
  .after((data) => {
    console.log('Logging submission:', data)
  })

enhanced({ name: 'Alice' })

不污染原型的纯函数版本:

js
function before(fn, beforeFn) {
  return function (...args) {
    beforeFn.apply(this, args)
    return fn.apply(this, args)
  }
}

function after(fn, afterFn) {
  return function (...args) {
    const result = fn.apply(this, args)
    afterFn.apply(this, args)
    return result
  }
}

function compose(...decorators) {
  return function (fn) {
    return decorators.reduceRight((decorated, decorator) => decorator(decorated), fn)
  }
}

const withLogging = (fn) => before(fn, (...args) => console.log('Call:', args))
const withTiming = (fn) => {
  return function (...args) {
    const start = performance.now()
    const result = fn.apply(this, args)
    console.log(`Duration: ${performance.now() - start}ms`)
    return result
  }
}

const enhance = compose(withLogging, withTiming)
const enhancedFetch = enhance(fetch)

TypeScript 装饰器语法

TypeScript 5.0+ 支持 TC39 Stage 3 的标准装饰器语法:

ts
function log(originalMethod: Function, context: ClassMethodDecoratorContext) {
  return function (this: any, ...args: any[]) {
    console.log(`Calling ${String(context.name)} with`, args)
    const result = originalMethod.apply(this, args)
    console.log(`Result:`, result)
    return result
  }
}

function readonly(
  _target: undefined,
  context: ClassFieldDecoratorContext
) {
  return function (this: any, initialValue: any) {
    return initialValue
  }
}

function sealed(constructor: Function, _context: ClassDecoratorContext) {
  Object.seal(constructor)
  Object.seal(constructor.prototype)
}

@sealed
class UserService {
  @log
  findUser(id: number) {
    return { id, name: 'Alice' }
  }

  @log
  deleteUser(id: number) {
    return { success: true }
  }
}

旧版实验性装饰器(experimentalDecorators)在 NestJS、Angular 等框架中广泛使用:

ts
function Controller(prefix: string) {
  return function (target: Function) {
    Reflect.defineMetadata('prefix', prefix, target)
  }
}

function Get(path: string) {
  return function (target: any, propertyKey: string) {
    Reflect.defineMetadata('method', 'GET', target, propertyKey)
    Reflect.defineMetadata('path', path, target, propertyKey)
  }
}

前端实际应用

  • React 高阶组件(HOC)本质是装饰器模式
  • NestJS 大量使用装饰器(@Controller@Get@Injectable
  • Angular 装饰器(@Component@Directive@Pipe
  • Mobx @observable@action
  • API 请求增强(添加 token、日志、错误处理)

适配器模式(Adapter)

概念

适配器模式将一个类的接口转换成客户端期望的另一个接口,使原本因接口不兼容而无法协作的类可以一起工作。在前端中,适配器常用于统一不同数据源的接口或兼容新旧 API。

UML 关系

┌──────────┐     ┌──────────────────┐     ┌───────────────┐
│  Client  │────▶│  Target          │     │  Adaptee      │
│          │     ├──────────────────┤     ├───────────────┤
└──────────┘     │ + request()     │     │ + legacyReq() │
                 └────────┬─────────┘     └───────┬───────┘
                          │                       │
                 ┌────────┴─────────┐             │
                 │    Adapter       │─────────────┘
                 ├──────────────────┤   uses
                 │ + request()     │
                 └──────────────────┘

代码实现

统一多个 HTTP 库的接口

js
class FetchAdapter {
  async request(config) {
    const { url, method = 'GET', headers = {}, data } = config

    const response = await fetch(url, {
      method,
      headers: {
        'Content-Type': 'application/json',
        ...headers
      },
      body: data ? JSON.stringify(data) : undefined
    })

    const responseData = await response.json()

    return {
      status: response.status,
      statusText: response.statusText,
      headers: Object.fromEntries(response.headers.entries()),
      data: responseData
    }
  }
}

class XHRAdapter {
  request(config) {
    return new Promise((resolve, reject) => {
      const { url, method = 'GET', headers = {}, data } = config
      const xhr = new XMLHttpRequest()

      xhr.open(method, url)

      Object.entries(headers).forEach(([key, value]) => {
        xhr.setRequestHeader(key, value)
      })

      xhr.onload = () => {
        resolve({
          status: xhr.status,
          statusText: xhr.statusText,
          headers: xhr.getAllResponseHeaders(),
          data: JSON.parse(xhr.responseText)
        })
      }

      xhr.onerror = () => reject(new Error('Network Error'))
      xhr.send(data ? JSON.stringify(data) : null)
    })
  }
}

class HttpClient {
  constructor(adapter) {
    this.adapter = adapter
  }

  get(url, config = {}) {
    return this.adapter.request({ ...config, url, method: 'GET' })
  }

  post(url, data, config = {}) {
    return this.adapter.request({ ...config, url, method: 'POST', data })
  }
}

const client = new HttpClient(new FetchAdapter())
client.get('/api/users')

后端接口数据适配

js
function adaptUserData(backendUser) {
  return {
    id: backendUser.user_id,
    name: `${backendUser.first_name} ${backendUser.last_name}`,
    email: backendUser.email_address,
    avatar: backendUser.avatar_url || '/default-avatar.png',
    createdAt: new Date(backendUser.create_time * 1000),
    role: backendUser.role_type === 1 ? 'admin' : 'user'
  }
}

function adaptPaginatedResponse(backendResponse) {
  return {
    items: backendResponse.data.list.map(adaptUserData),
    total: backendResponse.data.total_count,
    page: backendResponse.data.page_no,
    pageSize: backendResponse.data.page_size,
    hasMore: backendResponse.data.page_no * backendResponse.data.page_size < backendResponse.data.total_count
  }
}

前端实际应用

  • Axios 的适配器机制(浏览器用 XMLHttpRequest,Node.js 用 http 模块)
  • 地图 SDK 适配(统一高德/百度/Google Maps 的调用接口)
  • 后端接口数据转换层(DTO → VO)
  • localStorage/sessionStorage/IndexedDB 统一存储接口
  • 旧版 jQuery 代码迁移到现代框架时的兼容层

用心学习,用代码说话 💻