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); // 10console.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 = 100console.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); // 100revoke() // 勾销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 : falseproxy.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,fooproxy.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哟