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);


下面这段代码中,proxyobj对象的原型,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

同一个拦截器能够设置多个拦挡操作,有时候,咱们会在对象外部设置外部属性,该外部属性以下划线_结尾,示意这些属性不应该被内部应用,联合getset办法,能够做到避免这些外部属性被内部读写。

// 拦挡办法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办法拦挡函数的调用,callapply操作,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

下面代码中,变量pProxy的实例,当它作为函数调用时,就会触发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函数(间接调用或callapply调用),都会被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属性。
proxyProxy实例
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哟