《javascript高级程序设计》学习笔记 | 9.3.代理模式
关注前端小讴,阅读更多原创技术文章
代理模式
跟踪属性访问
- 通过捕获
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',触发get()拦截
proxy.age = 27 // 'Setting age=27',触发set()拦截
隐藏属性
- 代理的内部实现对外部代码不可见,可隐藏目标对象的指定属性
const hiddenProperties = ['foo', 'bar'] // 要隐藏的键
const targetObject = {
// 目标对象
foo: 1,
bar: 2,
baz: 3,
}
const proxy2 = 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 undefined // 隐藏属性
} else {
return Reflect.get(...arguments)
}
},
})
// get()拦截
console.log(proxy2.foo) // undefined,在代理内部被隐藏
console.log(proxy2.bar) // undefined,在代理内部被隐藏
console.log(proxy2.baz) // 3
// has()拦截
console.log('foo' in proxy2) // false,在代理内部被隐藏
console.log('bar' in proxy2) // false,在代理内部被隐藏
console.log('baz' in proxy2) // true
属性验证
- 所有的赋值操作都会触发
set()
捕获器,可根据所赋的值决定允许还是拒绝赋值
const target = {
onlyNumberGoHere: 0,
}
const proxy3 = new Proxy(target, {
set(target, property, value) {
if (typeof value !== 'number') {
return false
} else {
return Reflect.set(...arguments)
}
},
})
proxy3.onlyNumberGoHere = 1 // 拦截操作,所赋的值为Number类型
console.log(proxy3.onlyNumberGoHere) // 1,赋值成功
proxy3.onlyNumberGoHere = '2' // 拦截操作,所赋的值为String类型
console.log(proxy3.onlyNumberGoHere) // 1,赋值失败
函数与构造函数参数验证
- 可对函数和构造函数参数进行审查,让函数只接收某数类型的值
function median(...nums) {
return nums.sort()[Math.floor(nums.length / 2)]
}
const proxy4 = new Proxy(median, {
apply(target, thisArg, argumentsList) {
for (const arg of argumentsList) {
if (typeof arg !== 'number') {
// 只接收Number类型
throw 'Non-number argument provided'
}
}
return Reflect.apply(...arguments)
},
})
console.log(proxy4(4, 7, 1)) // 4
console.log(proxy4(4, 7, '1')) // Error: Non-number argument provided
- 可要求实例化时必须给构造函数传参
class User {
constructor(id) {
this._id = id
}
}
const proxy5 = new Proxy(User, {
construct(target, argumentsList, newTarget) {
if (argumentsList[0] === undefined) {
// 必须传参
throw 'User cannot be instantiated without id'
}
return Reflect.construct(...arguments)
},
})
new proxy5(1)
// new proxy5() // Error: 'User cannot be instantiated without id'
数据绑定与可观察对象
- 通过代理把运行时原本不相关的部分联系到一起:可将被代理的类绑定到一个全局集合,让所有创建的实例都被添加到该集合中
const userList = []
class User2 {
constructor(name) {
this._name = name
}
}
const proxy6 = new Proxy(User2, {
construct() {
const newUser = Reflect.construct(...arguments)
userList.push(newUser) // 将实例添加到全局集合
return newUser
},
})
new proxy6('John')
new proxy6('Jacob')
new proxy6('Jake')
console.log(userList) // [ User2 { _name: 'John' }, User2 { _name: 'Jacob' }, User2 { _name: 'Jake' } ]
- 可把集合绑定到一个事件分派程序,每次插入新实例时发送消息
const eventList = []
function emit(newValue) {
console.log(newValue)
/*
John
Jacob
*/
}
const proxy7 = new Proxy(eventList, {
set(target, property, value, receiver) {
console.log(target, property, value)
/*
[] 0 John
[ 'John' ] length 1
[ 'John' ] 1 Jacob
[ 'John', 'Jacob' ] length 2
*/
const result = Reflect.set(...arguments)
if (result && property !== 'length') {
emit(value)
}
return result
},
})
proxy7.push('John')
proxy7.push('Jacob')
总结 & 问点
- 使用代理写一段代码,监控对象在何时何处被访问
- 使用代理写一段代码,隐藏目标对象的指定属性
- 使用代理写一段代码,根据所赋的值决定允许或拒绝给对象赋值
- 使用代理写一段代码,让函数只接收 String 类型的参数
- 使用代理写一段代码,让构造函数实例化时必须传参
- 使用代理写一段代码,让被代理的类在实例化时添加到全局集合
- 使用代理写一段代码,让被代理的集合在每次插入数据时发送消息