代理模式

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

问题

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

解答

代理模式为对象提供一个代理,用来控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。

基本结构

// 目标对象
const target = {
  name: '张三',
  age: 25
}

// 代理对象
const proxy = new Proxy(target, {
  get(target, key) {
    console.log(`读取 ${key}`)
    return target[key]
  },
  set(target, key, value) {
    console.log(`设置 ${key} = ${value}`)
    target[key] = value
    return true
  }
})

proxy.name      // 读取 name -> '张三'
proxy.age = 26  // 设置 age = 26

应用场景一:数据验证

// 创建带验证的用户对象
function createUser(user) {
  return new Proxy(user, {
    set(target, key, value) {
      if (key === 'age' && typeof value !== 'number') {
        throw new TypeError('age 必须是数字')
      }
      if (key === 'age' && (value < 0 || value > 150)) {
        throw new RangeError('age 必须在 0-150 之间')
      }
      target[key] = value
      return true
    }
  })
}

const user = createUser({ name: '李四', age: 20 })
user.age = 25      // 正常
user.age = -1      // 抛出 RangeError
user.age = '二十'  // 抛出 TypeError

应用场景二:缓存代理

// 缓存计算结果
function createCachedFunction(fn) {
  const cache = new Map()
  
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args)
      
      if (cache.has(key)) {
        console.log('从缓存读取')
        return cache.get(key)
      }
      
      const result = target.apply(thisArg, args)
      cache.set(key, result)
      return result
    }
  })
}

// 模拟耗时计算
function heavyCompute(n) {
  console.log('计算中...')
  return n * n
}

const cachedCompute = createCachedFunction(heavyCompute)
cachedCompute(10)  // 计算中... -> 100
cachedCompute(10)  // 从缓存读取 -> 100

应用场景三:图片懒加载

// 虚拟代理实现图片懒加载
class ProxyImage {
  constructor(targetImage) {
    this.targetImage = targetImage
  }
  
  setSrc(src) {
    // 先显示占位图
    this.targetImage.src = 'loading.gif'
    
    // 加载真实图片
    const img = new Image()
    img.onload = () => {
      this.targetImage.src = src
    }
    img.src = src
  }
}

// 使用
const imgElement = document.querySelector('img')
const proxyImage = new ProxyImage(imgElement)
proxyImage.setSrc('https://example.com/large-image.jpg')

应用场景四:访问控制

// 保护代理:控制访问权限
function createProtectedObject(obj, allowedKeys) {
  return new Proxy(obj, {
    get(target, key) {
      if (!allowedKeys.includes(key)) {
        throw new Error(`无权访问 ${key}`)
      }
      return target[key]
    },
    set(target, key, value) {
      if (!allowedKeys.includes(key)) {
        throw new Error(`无权修改 ${key}`)
      }
      target[key] = value
      return true
    }
  })
}

const data = { public: '公开', private: '私密' }
const protectedData = createProtectedObject(data, ['public'])

protectedData.public   // '公开'
protectedData.private  // 抛出错误:无权访问 private

应用场景五:响应式数据(Vue 3 原理)

// 简化版响应式实现
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)  // 收集依赖
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)  // 触发更新
      return true
    }
  })
}

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

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

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

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

关键点

  • 代理模式:在目标对象前设置一层拦截,控制对目标的访问
  • ES6 Proxy:支持 13 种拦截操作(get、set、has、deleteProperty 等)
  • 常见应用:数据验证、缓存、懒加载、访问控制、响应式系统
  • 与装饰器区别:代理控制访问,装饰器增强功能
  • Vue 3 响应式:基于 Proxy 实现,比 Vue 2 的 Object.defineProperty 更强大