代理模式
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 更强大
目录