代理的定义
提供一些机制对外界的访问(对象或函数)进行过滤或重写,具体是将对象/方法的一些操作拦截过来,使用代理中相关的拦截方法去进行自定义的逻辑操作
Proxy基本使用方法
使用new Proxy
定义代理的时候需要传2个参数
let p = new Proxy(target, handler)
- 参数1: 当前将要拦截/包装的对象或方法
- 参数2: 代理的配置,定义一些拦截的钩子函数
// 当前的Proxy对obj进行了代理
let obj = {}
let p = new Proxy(obj,{})
p.name = "ES6"
console.log(obj.name); // ES6
for(let key in obj){
console.log(key); // name
}
常用拦截操作
Object.defineProperty
ES5中提供了一个方法可以对对象的属性进行拦截
let obj = {}
let newValue = ''
// 参数1:拦截的对象;参数2:拦截的对象的属性,参数3:是个对象,配置拦截的方法
Object.defineProperty(obj,'name',{
get(){
console.log('get')
return newValue
},
// 不能这样写,obj.name = 'ES6'时调用set,set的时候this.name = val,给name赋值的时候又会调用get,进入死循环
// set(val){
// console.log('set');
// this.name = val
// }
set(val){
console.log('set');
newValue = val
}
})
// 存 ==> 调用set方法
obj.name = 'ES6'
// 取 ==> 调用get方法
console.log(obj.name);
get
拦截对象属性的读取,比如proxy.foo和proxy['foo']。当我们想获取代理对象的值的时候,就会自动的调用get钩子函数去处理一些内容,在get里面可以进行一些业务逻辑的操作
get接受2个参数,第一个参数是将要拦截的对象,第2个参数是要传的参数,eg:
let arr = [1,2,3,4]
arr = new Proxy(arr,{
get(target,prop){
console.log(target,prop);
}
})
console.log(arr[1]); //[1, 2, 3, 4] '1'
eg: 使用get拦截数组,判断数组的某个下标下是否有值,有值就返回相应的值,没值就抛出一个error
// 对数组的代理
let arr = [1,2,3,4]
arr = new Proxy(arr,{
get(target,prop){
return prop in target ? target[prop] : '下标没有对应的值'
}
})
console.log(arr[1]); //2
eg: 实现一个字典的功能
// 对对象的代理
let dict = {
'hello' : '你好',
'world' : '世界'
}
dict = new Proxy(dict,{
get(target,prop){
return prop in target ? target[prop] : '当前词典无此词汇'
}
})
console.log(dict['hello']); // 你好
set
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。接收三个参数,第一个参数:拦截的目标对象,第二个参数是将要拦截的属性,第三个参数是将要拦截对象的属性对应的值
eg: 对数组进行设置值的时候进行代理和拦截,只能存储number类型,如果往数组中添加其他类型的值就会报错
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)
arr.push(6)
console.log(arr[0]); // 5
console.log(arr[1]); // 6
使用对象的代理,并不会破坏对象原有的方法,如:使用数组的push方法也会被set这个拦截钩子拦截
has
拦截propKey in proxy的操作,返回一个布尔值
let range = {
start: 1,
end: 5
}
range = new Proxy(range,{
has(target, prop) {
return prop>=target.start && prop<=target.end
}
})
console.log(2 in range); // true
console.log(9 in range); // false
ownKeys
循环遍历对象属性的时候使用,拦截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
eg: 复习下不同循环遍历方法之间的差别
let obj = {
name: 'lee',
[Symbol('es')]: 'ES6'
}
// 用于遍历key不是synmol类型的key
console.log(Object.getOwnPropertyNames(obj)); // ['name']
// 只能遍历symbol类型的key
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(es)]
console.log(Object.keys(obj)); // ['name']
for(let key in obj){
console.log(key); // name
}
eg; 不想让别人遍历到以下划线开头的私有属性
let userInfo = {
username: 'lee',
age: 32,
_password: '****'
}
userInfo = new Proxy(userInfo,{
ownKeys(target) {
return Object.keys(target).filter( key => !key.startsWith('_'))
}
})
for(let key in userInfo) {
console.log(key); // username age
}
console.log(Object.keys(userInfo)); // ['username', 'age']
deleteProperty
拦截删除操作(delete)
拦截delete proxy[propKey]的操作,返回一个布尔值。
eg: 以下划线开头的属性作为私有属性,需要用Proxy阻止对下划线开头的属性的任何访问,包括获取、设置、删除、循环遍历,因此我们就要使用get、set、deleteProperty代理的钩子函数
let user = {
name: 'lee',
age: 32,
_password: 'xxx'
}
user = new Proxy(user,{
// 拦截获取属性
get(target,prop) {
if(prop.startsWith('_')){
throw new Error('私有属性不可访问')
}else{
return target[prop]
}
},
// 拦截设置属性,对于set一定要返回布尔类型的值
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(item => !item.startsWith('_'))
}
})
console.log(user._password); // 私有属性不可访问
console.log(user.name); // lee
user.age = 18
console.log(user.age); // 18
try {
user._password = 8888
} catch (e) {
console.log(e.message); // 私有属性不可访问
}
try {
delete user.age
} catch (e) {
console.log(e.message);
}
console.log(user.age); // undefined
try {
delete user._password
} catch (e) {
console.log(e.message); // 不可删除
}
for(key in user) {
console.log(key); // name age
}
apply
接收三个参数,第一个参数是拦截的目标,第二个参数是当前的上下文,第三个参数是目标对象参数的数组
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
eg: 对求和的结果进行拦截,输出的结果为和的2倍
let sum = (...args) => {
let num = 0
args.forEach( item =>{
num += item
})
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,3)); // 12
console.log(sum.apply(null,[1,2,3])); // 12
construct
拦截new,接收三个参数,第一个参数是拦截对象,第二个参数是class内部constructor的参数列表,第三个参数是创建实例的时候new作用的构造函数
拦截 Proxy 实例作为构造函数调用的操作,返回一个对象,比如new proxy(...args)。
let User = class {
constructor(name){
this.name = name
}
}
User = new Proxy(User,{
construct(target,args,newTarget) {
return new target(...args)
}
})
console.log(new User('lee')); // {name: 'lee'}