Proxy Pattern
JavaScript 代理模式的实现与应用场景
问题
什么是代理模式?如何在 JavaScript 中实现代理模式?
解答
代理模式是一种结构型设计模式,通过创建一个代理对象来控制对原始对象的访问。ES6 提供了原生的 Proxy 对象来实现这一模式。
基本语法
const proxy = new Proxy(target, handler)
// target: 被代理的对象
// handler: 定义拦截行为的对象
数据验证
// 创建一个带验证的用户对象
const userValidator = {
set(target, prop, value) {
if (prop === 'age') {
if (typeof value !== 'number') {
throw new TypeError('age 必须是数字')
}
if (value < 0 || value > 150) {
throw new RangeError('age 必须在 0-150 之间')
}
}
target[prop] = value
return true
}
}
const user = new Proxy({}, userValidator)
user.name = 'Tom' // 正常
user.age = 25 // 正常
// user.age = -1 // RangeError: age 必须在 0-150 之间
// user.age = 'abc' // TypeError: age 必须是数字
属性访问控制
// 私有属性保护
const privateHandler = {
get(target, prop) {
if (prop.startsWith('_')) {
throw new Error(`无法访问私有属性 ${prop}`)
}
return target[prop]
},
set(target, prop, value) {
if (prop.startsWith('_')) {
throw new Error(`无法修改私有属性 ${prop}`)
}
target[prop] = value
return true
}
}
const obj = new Proxy({ _secret: '123', name: 'test' }, privateHandler)
console.log(obj.name) // 'test'
// console.log(obj._secret) // Error: 无法访问私有属性 _secret
函数调用拦截
// 记录函数调用日志
function createLoggedFunction(fn) {
return new Proxy(fn, {
apply(target, thisArg, args) {
console.log(`调用 ${target.name},参数:`, args)
const result = target.apply(thisArg, args)
console.log(`返回值:`, result)
return result
}
})
}
function add(a, b) {
return a + b
}
const loggedAdd = createLoggedFunction(add)
loggedAdd(1, 2)
// 调用 add,参数: [1, 2]
// 返回值: 3
缓存代理
// 缓存计算结果
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 expensiveCalculation(n) {
console.log('执行计算...')
return n * n
}
const cachedCalc = createCachedFunction(expensiveCalculation)
cachedCalc(5) // 执行计算... 25
cachedCalc(5) // 从缓存获取 25
cachedCalc(3) // 执行计算... 9
响应式数据(Vue 3 原理)
// 简化版响应式实现
function reactive(obj) {
return new Proxy(obj, {
get(target, prop) {
console.log(`读取 ${prop}`)
// 实际实现中这里会收集依赖
return target[prop]
},
set(target, prop, value) {
console.log(`设置 ${prop} = ${value}`)
target[prop] = value
// 实际实现中这里会触发更新
return true
}
})
}
const state = reactive({ count: 0 })
state.count // 读取 count
state.count = 1 // 设置 count = 1
常用 handler 方法
const handler = {
get(target, prop, receiver) {}, // 读取属性
set(target, prop, value, receiver) {}, // 设置属性
has(target, prop) {}, // in 操作符
deleteProperty(target, prop) {}, // delete 操作符
apply(target, thisArg, args) {}, // 函数调用
construct(target, args) {}, // new 操作符
getPrototypeOf(target) {}, // Object.getPrototypeOf
setPrototypeOf(target, proto) {}, // Object.setPrototypeOf
ownKeys(target) {}, // Object.keys 等
defineProperty(target, prop, desc) {}, // Object.defineProperty
}
关键点
Proxy可以拦截对象的 13 种操作,比Object.defineProperty更强大handler中的方法称为”陷阱”(trap),用于自定义拦截行为- Vue 3 使用 Proxy 替代 Vue 2 的
Object.defineProperty实现响应式 - Proxy 可以代理数组,能拦截
push、pop等方法 Reflect对象通常与 Proxy 配合使用,提供默认行为
目录