Proxy Pattern

代理模式的实现与前端应用场景

问题

什么是代理模式?在前端开发中有哪些应用场景?

解答

代理模式为对象提供一个代理,用来控制对这个对象的访问。代理对象和本体对象具有相同的接口,客户端无需区分。

ES6 Proxy 基础用法

// 原始对象
const target = {
  name: 'Alice',
  age: 25
}

// 创建代理
const proxy = new Proxy(target, {
  // 拦截读取操作
  get(target, prop) {
    console.log(`读取属性: ${prop}`)
    return target[prop]
  },
  
  // 拦截设置操作
  set(target, prop, value) {
    console.log(`设置属性: ${prop} = ${value}`)
    target[prop] = value
    return true
  }
})

proxy.name      // 读取属性: name -> 'Alice'
proxy.age = 26  // 设置属性: age = 26

虚拟代理:图片懒加载

// 本体:负责设置图片 src
const imageLoader = {
  setSrc(img, src) {
    img.src = src
  }
}

// 代理:先显示 loading,加载完成后替换
const proxyImageLoader = {
  setSrc(img, src) {
    // 先显示占位图
    imageLoader.setSrc(img, '/loading.gif')
    
    // 后台加载真实图片
    const realImg = new Image()
    realImg.onload = () => {
      imageLoader.setSrc(img, src)
    }
    realImg.src = src
  }
}

// 使用代理加载图片
const img = document.querySelector('#myImage')
proxyImageLoader.setSrc(img, 'https://example.com/large-image.jpg')

缓存代理:计算结果缓存

// 耗时计算函数
function heavyCompute(n) {
  console.log('执行计算...')
  let result = 0
  for (let i = 1; i <= n; i++) {
    result += i
  }
  return result
}

// 创建缓存代理
function createCacheProxy(fn) {
  const cache = new Map()
  
  return function(...args) {
    const key = JSON.stringify(args)
    
    if (cache.has(key)) {
      console.log('命中缓存')
      return cache.get(key)
    }
    
    const result = fn.apply(this, args)
    cache.set(key, result)
    return result
  }
}

const cachedCompute = createCacheProxy(heavyCompute)

cachedCompute(10000)  // 执行计算... -> 50005000
cachedCompute(10000)  // 命中缓存 -> 50005000

保护代理:访问控制

// 用户数据
const user = {
  name: 'Alice',
  password: 'secret123',
  email: 'alice@example.com'
}

// 创建保护代理
const protectedUser = new Proxy(user, {
  get(target, prop) {
    // 禁止访问敏感字段
    if (prop === 'password') {
      throw new Error('无权访问密码')
    }
    return target[prop]
  },
  
  set(target, prop, value) {
    // 禁止修改某些字段
    if (prop === 'name') {
      throw new Error('用户名不可修改')
    }
    target[prop] = value
    return true
  }
})

protectedUser.email           // 'alice@example.com'
protectedUser.password        // Error: 无权访问密码
protectedUser.name = 'Bob'    // Error: 用户名不可修改

数据响应式(Vue 3 原理简化版)

function reactive(obj) {
  return new Proxy(obj, {
    get(target, prop) {
      // 收集依赖
      track(target, prop)
      return target[prop]
    },
    
    set(target, prop, value) {
      target[prop] = value
      // 触发更新
      trigger(target, prop)
      return true
    }
  })
}

// 简化的依赖收集和触发
const effects = new Map()

function track(target, prop) {
  console.log(`收集依赖: ${prop}`)
}

function trigger(target, prop) {
  console.log(`触发更新: ${prop}`)
}

const state = reactive({ count: 0 })
state.count      // 收集依赖: count
state.count = 1  // 触发更新: count

关键点

  • 代理和本体接口一致,对使用者透明
  • 虚拟代理延迟创建开销大的对象(懒加载)
  • 缓存代理避免重复计算,提升性能
  • 保护代理控制访问权限
  • ES6 Proxy 可拦截 13 种操作(get、set、has、deleteProperty 等)