Proxy
Proxy
(代理),首先了解代理是什么意思,能力更便于理解 Proxy
的作用。Proxy
是一个代理,能够这么了解,就相当于它是一个快递代理点,快递会运输到该代理点,咱们取件只须要去对应的代理点取件即可,代理点说快递没到就是没到,代理点说要出示证件就要出示证件。Proxy
代理的是一个对象,该对象被代理后咱们就不能间接拜访,须要通过代理拜访。咱们想要获取对象内的某个值,代理说有就有,说没有就没有,代理返回的值长这样,这个值它就是这样,这就是代理,也能够将它了解成拦挡。
创立 Proxy
语法:let proxy = new Proxy(target,handler)
new Proxy()
用来生成 Proxy
实例 target
参数示意要拦挡的指标对象 handler
参数是一个对象,用来制订拦挡行为
案例一:
let figure = {name:'东方不败'}
// 创立代理的办法,当通过代理拜访指标对象时,此对象中的对应办法会执行
let handlers = {get(target,prop){return '我是代理,我说返回什么就是什么'}
}
// 创立代理:代理的指标对象、代理的操作
let proxys = new Proxy(figure,handlers)
console.log(proxys.name); // 我是代理,我说返回什么就是什么
console.log(proxys.age); // 我是代理,我说返回什么就是什么
下面的例子就是利用 Proxy
,通过Proxy
拜访想要的值时,代理对取值进行拦挡,返回给咱们指定的信息,在这里,不管咱们怎么取值,返回的都是 我是代理,我说返回什么就是什么
。
案例二
let proxy2 = new Proxy({},{get: function(target,propKey){return 10;}
})
proxy2.num = 20;
console.log(proxy2.num); // 10
console.log(proxy2.name); // 10
下面代码中,咱们给 Proxy
两个参数,第一个参数:要代理的指标对象(也就是 {}
), 第二个参数:配置对象(被代理对象的操作),在这里,配置对象有个get
办法,用来拦挡指标对象属性的拜访申请。
通过 Proxy
拜访属性的时候,get
办法将拜访的这一层拦挡了,这里拦挡函数返回的10
,所以拜访任何属性失去的都是10
。也就是说,给指标对象增加了属性值,然而在拜访这一层被拦挡了,任何拜访都会返回拦挡的这一层。
get
办法的两个参数别离是指标对象和所要拜访的属性,这里没有做任何操作,间接返回10
,所以咱们获取的值都是10
。
留神:如果想要 Proxy
失效,必须操作 Proxy
的实例,操作原对象是没有任何成果的。
案例三
如果 Proxy
的第二个参数(配置对象)没有设置任何拦挡,就等同于间接拜访原对象。
let target = {}
let handler = {}
let proxy = new Proxy(target,handler)
proxy.time = 20;
console.log(proxy.time); // 20
//handler 没有设置任何拦挡成果,拜访 proxy 就等于拜访 target
对象内是能够设置函数的,能够将 Proxy
对象设置到 object.proxy
属性,这样就能够在 object
对象上调用
let object = {proxy : new Proxy(target,handler) }
// object.proxy 调用
Proxy
实例也能够作为其余对象的原型对象
let proxy = new Proxy({},{get : function(target,propKsy){return 10}
})
let obj = Object.create(proxy)
obj.time = 10;
console.log(obj);
下面这段代码中,proxy
是 obj
对象的原型,obj
对象自身没有 time
属性,所以依据原型链,会在 proxy
对象上读取该对象。
Proxy 的实例办法
get()
get()
用于拦挡某个属性的读取操作,能够承受三个参数:
1、指标对象
2、属性名
3、Proxy
实例自身(操作指向的对象),该参数为可选参数。
let person = {name : '张三'}
let proxy = new Proxy(person,{get:function(target,propKey){
// 判断对象上是否存在该属性名
if(propKey in target){
// 存在,返回该属性
return target[propKey]
} else {
// 不存在,抛出谬误
throw new ReferenceError("属性名:" + propKey + "不存在")
}
}
})
console.log(proxy.name); // 张三
// console.log(proxy.age); // Uncaught ReferenceError: 属性名:age 不存在
in
运算符:in
操作符用来判断某个属性属于
某个对象,能够是对象的间接属性,也能够是通过prototype
继承的属性。
下面这段代码示意,如果拜访指标不存在则抛出谬误。如果没有设置拦挡函数,拜访不存在的属性会返回undefined
。
set()
set()
办法用来拦挡某个属性的赋值操作,接管四个参数,顺次为:
1、指标对象
2、属性名
3、属性值
4、Proxy
实例自身 (可选)
案例一
判断属性值是否大于 200
,大于200
则报错,并且属性值必须是数字
let numHold = {set: function(obj,prop,value){if(prop === 'age'){if(!Number(value)){throw new TypeError('参数不是数字')
}
if(value > 200){throw new RangeError('参数不能大于 200')
}
}
// 如果条件满足则间接保留, 将值赋值给该属性
obj[prop] = value
}
}
let persons = new Proxy({},numHold)
persons.age = 100
console.log(persons.age); // 100
// persons.age = 300 // 报错,参数不能大于 200
// persons.age = '西方' // 报错,参数不是数字
下面代码中,设置了一个存值函数 set
,对对象的age
属性进行赋值时,如果不满足 age
属性的赋值要求则会抛出一个相应的谬误,其实这种写法就相当于是在做数据验证。
利用
set
办法还可进行数据绑定,每当数据发生变化时,自动更新DOM
同一个拦截器能够设置多个拦挡操作,有时候,咱们会在对象外部设置外部属性,该外部属性以下划线 _
结尾,示意这些属性不应该被内部应用,联合 get
和set
办法,能够做到避免这些外部属性被内部读写。
// 拦挡办法
let handler = {
// 读取
get(target,key){invariant(key,'get')
return target[key]
},
// 写入
set(target,key,value){invariant(key,'set');
target[key] = value
return true
}
}
function invariant(key,action){
// 属性名的第一个字符是_则为公有办法,不容许内部读写
if(key[0] === '_'){throw new Error(`${action}公有属性 ${key}有效 `)
}
}
let target = {} // 以后对象
let proxy = new Proxy(target,handler)
// proxy._prop; // get 公有属性_prop 有效
// proxy._name = '东方不败' // set 公有属性_name 有效
apply()
apply
办法拦挡函数的调用,call
和 apply
操作,apply
办法能够承受三个参数,别离是:
1、指标对象
2、指标对象的上下文对象(this
)
3、指标对象的 参数数组
案例一
let target = function(){return '东方不败'}
let handler = {apply:function(){return '我是 Proxy'}
}
let p = new Proxy(target,handler)
console.log(p()); // 我是 Proxy
下面代码中,变量 p
为Proxy
的实例,当它作为函数调用时,就会触发 apply
办法拦挡,返回 apply
的返回后果
案例二
let twice = {apply(target,ctx,args){// console.log(Reflect.apply(...arguments)); // 3
return Reflect.apply(...arguments) * 2
}
}
function sum(left,right){return left + right // 1 + 2}
let proxy6 = new Proxy(sum,twice)
console.log(proxy6(1,2)); // 6
下面代码中,每当执行 Proxy
函数(间接调用或 call
和apply
调用),都会被 apply
办法拦挡。
console.log(proxy6.call(null,2,3)); // 10
has()
has()
办法用来拦挡 HasProperty
操作,即判断对象是否具备某个属性,该办法会失效。典型的操作就是 in
运算符。has()
办法承受两个参数:
1、指标对象
2、须要查问的属性名
// 应用 has()办法暗藏某些属性,不被 in 运算符发现
let handler = {has(target,key){// key 为传入的属性名,这里 key[0]就是属性名的第一个字符
if(key[0] === '_'){
// 第一个字符是_则返回 false
return false
}
return key in target
}
}
let target = {_prop:'foo',prop:'fuu'}
let proxy = new Proxy(target,handler)
console.log('_prop' in proxy); // false '_prop' 属性不属于 proxy 对象
console.log('_prop' in target); // true '_prop' 属性属于 target 对象
留神:
如果原对象不可配置或禁止扩大,这是has()
拦挡就会报错
尽管for...in
循环也用到了了in
运算符,然而has()
拦挡对for...in
循环不失效。
construct()
construct()
办法用于拦挡 new
命令,当对 Proxy
实例应用 new
命令的时候触发 construct()
承受三个参数:
1、指标对象
2、构造函数的 参数数组
3、创立实例对象时,new
命令作用的构造函数(也就是上面例子中的p2
)
let con = {construct:function(target,arg){// target 是一个函数(){}
// args 是一个参数数组
// this : construct
console.log(this === con); // true
console.log('回调:'+arg.join(',')); // 回调:1,2
return {value : arg[0] * 10 } // construct 返回的必须是一个对象,否则报错
// return 1 // 返回的不是对象,报错 'construct' on proxy: trap returned non-object ('1')
}
}
let p = new Proxy(function(){},con)
console.log((new p(1,2).value)); // new p()触发 construct 拦挡
留神:因为
construct()
拦挡的是构造函数,所以它的指标对象必须是函数,否则会报错
留神:construct()
中的this
指向的是con
,而不是实例对象
deleteProperty()
deleteProperty
办法用于拦挡 delete
操作,如果这个办法抛出谬误或返回 false
,以后属性就无奈被delete
命令删除,当对 Proxy
实例应用 delete
命令的时候触发
let del = {deleteProperty(target,key){invariant(key,'delete')
delete target[key] // 如 invariant 未抛出谬误就证实是能够删除的,删除操作
return true // 抛出 true
}
}
function invariant(key,action){
// 如果属性名的第一个字符是_阐明是公有属性,抛出谬误
if(key[0] === '_'){throw new Error(` 以后操作:${action}对于公有属性 ${key}有效 `)
}
}
let target = {_prop:'foo'}
let proxy = new Proxy(target,del)
// console.log(delete proxy._prop); // 报错:以后操作:delete 对于公有属性_prop 有效
留神,指标对象本身的不可配置(
configurable
)的属性,不能被deleteProperty
办法删除,否则报错。
defineProperty()
defineProperty()
办法拦挡 Object.defineProperty()
操作
let h = {defineProperty (target,key,desc){
// target 指标对象
// 指标对象的属性名
// desc 指标对象的赋值
return false
// return target[key] = desc.value
}
}
let t = {}
let pr = new Proxy(t,h)
console.log(pr.foo = 'bar'); // 不会失效,被拦挡
console.log(t); // {}
下面代码中,defineProperty()
办法外部没有任何操作,只返回 false
,导致新增加的属性总是有效。
这里返回的 false
只是用来提醒操作失败,自身并不能阻止增加新属性。
Proxy.revocable()
Proxy.revocable()
办法返回一个可勾销的 Proxy
实例。
let target = {}
let handler = {}
let {proxy, revoke} = Proxy.revocable(target , handler);
console.log(proxy.foo = 100); // 100
revoke() // 勾销 Proxy 实例
console.log(proxy.foo); // Cannot perform 'get' on a proxy that has been revoked
Proxy.revocable()
返回一个对象,该对象内有 proxy
属性和 revoke
属性。proxy
是 Proxy
实例 revoke
是一个函数,用来勾销 Proxy
实例
下面代码中 revoke
执行完后,勾销了 Proxy
实例,当再次拜访 Proxy
实例时会报错。Proxy.revocable()
的一个应用场景:指标对象不容许间接拜访,必须通过代理拜访,一但拜访完结,就是发出代理权,不容许再次拜访。
this 问题
尽管 Proxy
能够代理针对指标对象的拜访,但它不是指标对象的通明代理,即不做任何拦挡的状况下,也无奈保障与指标对象的行为统一。次要起因就是在 Proxy
代理的状况下,指标对象外部的 this
关键字会指向 Proxy
代理。
let target = {m : function () {console.log('proxy',this);
// m:() false
// Proxy {m: ƒ} true
console.log(this === proxy); // target === proxy : false
}
}
let handler = {}
let proxy = new Proxy(target,handler)
target.m(); // target === proxy : false
proxy.m(); // proxy === proxy : true
失常状况下,对象内函数的 this
指向为对象自身,也就是下面代码的 target
,然而下面代码中Proxy
代理了 target
,一旦proxy
代理 target
对象,那么 target.m()
外部的 this
就是指向 proxy
,而不是target
。所以,尽管proxy
没有做任何拦挡,但 target.m()
和proxy.m()
会返回不一样的后果。
案例一
因为 this
指向的变动,导致 Proxy
无奈代理指标对象
let _name = new WeakMap() // 将值保留在这里
class Person {constructor(name){_name.set(this,name) // set 一个_name 等于 name
}
get name(){return _name.get(this) // 返回_name 的值
}
}
let jane = new Person('东方不败')
console.log(jane.name); // 东方不败
let proxy2 = new Proxy(jane,{})
console.log(proxy2.name); // undefined, 这里的 this 指向的是 Proxy,所以找不到值
下面代码中,指标对象 东方不败
的name
属性,理论保留在内部 WeakMap
对象 _name
下面,通过 this
键辨别。因为通过 proxy2.name
拜访时,this
指向proxy2
,导致无奈取到值,所以返回undefined
。
此外,有些原生对象的外部属性,只有通过正确的 this
能力拿到,所以 Proxy
也无奈代理这些原生对象的属性
let t = new Date()
let h = {}
let p = new Proxy(t,h)
// console.log(p.getDate()); // 报错 this is not a Date object.
console.log(t.getDate()); // 8
下面代码中,getData()
办法只能在 Date
对象实例下面拿到,如果 this
不是 Date
对象实例就会报错。这时,this
绑定原始对象,就能够解决这个问题。
let t2 = new Date('2023-01-01')
let h2 = {get(target,prop){if(prop === 'getDate'){
// 更改 this 指向,绑定原始对象
return target.getDate.bind(target)
}
return Reflect.get(target,prop);
}
}
let p2 = new Proxy(t2,h2)
p2.getDate // 8
bind()
办法次要就是将函数绑定到某个对象,bind()
会创立一个函数,函数体内的this
对象的值会被绑定到传入bind()
第一个参数的值。
另外,Proxy
拦挡函数外部的 this
,指向的是以后对象,对象内函数都是指向以后对象,其实就是对象内函数的this
指向问题。
let handler = {get:function(target,key,receiver){return 'hello,'+key},
set:function(target,key,value){console.log(this === handler); // true
target[key] = value
return true
}
}
let proxy = new Proxy({},handler)
console.log(proxy.foo); // hello,foo
proxy.foo = 1 // 触发 set 办法
对于对象内函数的 this
指向问题,请看另一篇:对象定义 - 解构 - 枚举属性遍历以及对象内函数
Proxy 反对的拦挡操作
1、get(target, propKey, receiver):拦挡对象属性的读取,比方 proxy.foo 和 proxy['foo']。2、set(target, propKey, value, receiver):拦挡对象属性的设置,比方 proxy.foo = v 或 proxy['foo'] = v,返回一个布尔值。3、has(target, propKey):拦挡 propKey in proxy 的操作,返回一个布尔值。4、deleteProperty(target, propKey):拦挡 delete proxy[propKey]的操作,返回一个布尔值。5、ownKeys(target):拦挡 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in 循环,返回一个数组。该办法返回指标对象所有本身的属性的属性名,而 Object.keys()的返回后果仅包含指标对象本身的可遍历属性。6、getOwnPropertyDescriptor(target, propKey):拦挡 Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的形容对象。7、defineProperty(target, propKey, propDesc):拦挡 Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。8、preventExtensions(target):拦挡 Object.preventExtensions(proxy),返回一个布尔值。9、getPrototypeOf(target):拦挡 Object.getPrototypeOf(proxy),返回一个对象。10、isExtensible(target):拦挡 Object.isExtensible(proxy),返回一个布尔值。11、setPrototypeOf(target, proto):拦挡 Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果指标对象是函数,那么还有两种额定操作能够拦挡。12、apply(target, object, args):拦挡 Proxy 实例作为函数调用的操作,比方 proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。13、construct(target, args):拦挡 Proxy 实例作为结构函数调用的操作,比方 new proxy(...args)。
案例源码:https://gitee.com/wang_fan_w/es6-science-institute
如果感觉这篇文章对你有帮忙,欢送点亮一下 star 哟