代理与反射-代理的常见使用场景
一 跟踪属性访问
通过捕获 get、set 和 has 等操作,可以知道对象属性什么时候被访问、被查询,把实现相应捕获器的某个对象代理放到应用中,可以监控这个对象何时在何处被访问过:
const user = {
name: 'Jake',
}
const proxy = new Proxy(user, {
get(target, property, receiver) {
console.log(`Getting ${property}`)
return Reflect.get(...arguments)
},
set(target, property, value, receiver) {
console.log(`Setting ${property}=${value}`)
return Reflect.set(...arguments)
},
})
proxy.name // Getting name
proxy.age = 27 // Setting age=27
二 隐藏属性
代理的内部实现对外部代码是不可见的,因此要隐藏目标对象上的属性也轻而易举:
const hiddenProperties = ['foo', 'bar']
const targetObject = {
foo: 1,
bar: 2,
baz: 3,
}
const proxy = new Proxy(targetObject, {
get(target, property) {
if (hiddenProperties.includes(property)) {
return undefined
} else {
return Reflect.get(...arguments)
}
},
has(target, property) {
if (hiddenProperties.includes(property)) {
return false
} else {
return Reflect.has(...arguments)
}
},
})
// get()
console.log(proxy.foo) // undefined
console.log(proxy.bar) // undefined
console.log(proxy.baz) // 3
// has()
console.log('foo' in proxy) // false
console.log('bar' in proxy) // false
console.log('baz' in proxy) // true
三 属性验证
因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值,从而进行属性验证:
const target = {
onlyNumbersGoHere: 0,
}
const proxy = new Proxy(target, {
set(target, property, value) {
if (typeof value !== 'number') {
return false
} else {
return Reflect.set(...arguments)
}
},
})
proxy.onlyNumbersGoHere = 1
console.log(proxy.onlyNumbersGoHere) // 1
proxy.onlyNumbersGoHere = '2'
console.log(proxy.onlyNumbersGoHere) // 1
四 函数与构造函数参数验证
跟保护和验证对象属性类似,也可对函数和构造函数参数进行审查。比如,可以让函数只接收某种类型的值:
function median(...nums) {
return nums.sort()[Math.floor(nums.length / 2)]
}
const proxy = new Proxy(median, {
apply(target, thisArg, argumentsList) {
for (const arg of argumentsList) {
if (typeof arg !== 'number') {
throw 'Non-number argument provided'
}
}
return Reflect.apply(...arguments)
},
})
console.log(proxy(4, 7, 1)) // 4
console.log(proxy(4, '7', 1)) // Error: Non-number argument provided
类似地,可以要求实例化时必须给构造函数传参:
class User {
constructor(id) {
this.id = id
}
}
const proxy = new Proxy(User, {
construct(target, argumentsList, newTarget) {
if (argumentsList[0] === undefined) {
throw 'User cannot be instantiated without id'
} else {
return Reflect.construct(...arguments)
}
},
})
new proxy(1)
new proxy() // Error: User cannot be instantiated without id
五 数据绑定
通过代理可以把运行时原本不相关的部分联系到一起。这样就可以实现各种模式,从而让不同的代码互操作。
比如,可以将被代理的类绑定到一个全局实例集合,让所有创建的实例都被添加到这个集合中:
const userList = []
class User {
constructor(name) {
this.name_ = name
}
}
const proxy = new Proxy(User, {
construct() {
const newUser = Reflect.construct(...arguments)
userList.push(newUser)
return newUser
},
})
new proxy('John')
new proxy('Jacob')
new proxy('Jingleheimerschmidt')
console.log(userList) // [User {}, User {}, User{}]
另外,还可以把集合绑定到一个事件分派程序,每次插入新实例时都会发送消息:
const userList = []
function emit(newValue) {
console.log(newValue)
}
const proxy = new Proxy(userList, {
set(target, property, value, receiver) {
const result = Reflect.set(...arguments)
if (result) {
emit(Reflect.get(target, property, receiver))
}
return result
},
})
proxy.push('John') // John
proxy.push('Jacob') // Jacob