Proxy
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
js
// es5 使用 Object.defineProperty 拦截对象
let obj = {}
let newVal = ''
Object.defineProperty(obj, 'name', {
get() {
console.log('--get')
return newVal
},
set(val) {
console.log('--set')
// this.name = val // VM2132:8 Uncaught RangeError: Maximum call stack size exceeded
newVal = val
}
})
console.log(obj.name)
obj.name = 'es'
console.log(obj.name)
// ------------------------------------------ //
let obj = {}
let p = new Proxy(obj, {})
p.name = 'hello'
console.log(obj.name)
for(let key in obj) {
console.log(key) // name
}
一些钩子方法:get set has ownKeys deleteProperty apply construct 。
js
// get
let arr = [1,2,3]
arr = new Proxy(arr, {
get(target, prop) {
console.log(target, prop)
return prop in target ? target[prop] : null
}
})
console.log(arr[3])
// set
let arr = []
arr = new Proxy(arr, {
set(target, prop, val) {
if(typeof val === 'number') {
target[prop] = val
return true
} else {
return false
}
}
})
arr.push(5)
console.log(arr[0], arr) // 5 Proxy {0: 5}
// has
let range = {
start: 1,
end: 6
}
range = new Proxy(range, {
has(target, prop) {
return prop >= target.start && prop <= target.end
}
})
console.log(2 in range) // true
// ownKeys
let obj = {
name: 'es6',
[Symbol('es')]: 'es6'
}
Object.getOwnPropertyNames(obj) // ['name']
Object.getOwnPropertySymbols(obj) // [Symbol(es)]
Object.keys(obj) // ['name']
for(let key in obj) {
console.log(key) // name
}
// ownKeys 能实现对对象的 key 进行拦截
// 场景:对 _password 隐藏
let user = {
name: 'lisi',
age: 22,
_password: '***'
}
for(let key in user) {
console.log(key) // name age _password
}
userInfo = new Proxy(user, {
ownKeys(target) {
return Object.keys(target)
.filter(key => !key.startsWith('_'))
}
})
for(let key in userInfo) {
console.log(key) // name age
}
Object.getOwnPropertyNames(obj) // ['name', 'age']
Object.keys(obj) // ['name', 'age']
// 删除 deleteProperty
let user = {
name: 'lisi',
age: 22,
_password: '***'
}
userInfo = new Proxy(user, {
get(target, prop) {
if(prop.startsWith('_')) {
throw new Error('不可访问')
} else {
return target[prop]
}
},
set(target, prop, val) {
if(prop.startsWith('_')) {
throw new Error('不可设置')
} else {
target[prop] = val
return true
}
},
deleteProperty(target, prop) {
if(prop.startsWith('_')) {
throw new Error('不可删除')
} else {
delete target[prop]
return true
}
},
ownKeys(target) {
return Object.keys(target)
.filter(key => !key.startsWith('_'))
}
})
userInfo._password = '123' // VM2654:16 Uncaught Error: 不可设置
console.log(userInfo._password) // Uncaught Error: 不可访问
delete userInfo._password // Uncaught Error: 不可删除
// apply
let sum = (...args) => {
let num = 0
args.forEach(i => num += i)
return num
}
sum = new Proxy(sum, {
apply(target, ctx, args) {
return target(...args) * 2
}
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2)) // 6
console.log(sum.apply(null, [1, 2])) // 6
// construct 拦截 new 操作符
let User = class {
constructor(name) {
this.name = name
}
}
User = new Proxy(User, {
construct(target, args, newTarget) {
return new target(...args)
}
})
console.log(new User('imooc')) // User {name: 'imooc'}
Reflect
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与 proxy handlers (en-US) 的方法相同。Reflect
不是一个函数对象,因此它是不可构造的。 将 Object 属于语言内部的方法放到 Reflect 上;修改某些 Object 方法的返回结果,让其变的更合理;让 Object 操作变成函数行为;Reflect 对象的方法与 Proxy 对象的方法一一对应。
js
// 将 Object 属于语言内部的方法放到 Reflect 上
let obj = {}
let newVal = ''
Reflect.defineProperty(obj, 'name', {
get() {
console.log('--get')
return newVal
},
set(val) {
console.log('--set')
// this.name = val // VM2132:8 Uncaught RangeError: Maximum call stack size exceeded
newVal = val
}
})
console.log(obj.name)
obj.name = 'es'
console.log(obj.name)
// 修改某些 Object 方法的返回结果
// 当 Object.defineProperty 某些属性无法定义会抛出异常,使用 try catch 包裹,不想使用 try catch 怎么办?用 Reflect.defineProperty,它返回值是 boolean 类型
if(Reflect.defineProperty()) {} else {}
// 让 Object 操作变成函数行为
console.log('assign' in Object) // true
console.log(Reflect.has(Object, 'assign')) // true
// Reflect 对象的方法与 Proxy 对象的方法一一对应
let user = {
name: 'lisi',
age: 22,
_password: '***'
}
userInfo = new Proxy(user, {
get(target, prop) {
if(prop.startsWith('_')) {
throw new Error('不可访问')
} else {
return Reflect.get(target, prop)
}
},
set(target, prop, val) {
if(prop.startsWith('_')) {
throw new Error('不可设置')
} else {
Reflect.set(target, prop, val)
return true
}
},
deleteProperty(target, prop) {
if(prop.startsWith('_')) {
throw new Error('不可删除')
} else {
delete Reflect.deleteProperty(target, prop)
return true
}
},
ownKeys(target) {
return Reflect.ownKeys(target)
.filter(key => !key.startsWith('_'))
}
})
// userInfo._password = '123' // VM2654:16 Uncaught Error: 不可设置
// console.log(userInfo._password) // Uncaught Error: 不可访问
delete userInfo.age
// apply
let sum = (...args) => {
let num = 0
args.forEach(i => num += i)
return num
}
sum = new Proxy(sum, {
apply(target, ctx, args) {
return Reflect.apply(target, target, [...args]) * 2
}
})
console.log(sum(1, 2)) // 6
console.log(sum.call(null, 1, 2)) // 6
console.log(sum.apply(null, [1, 2])) // 6