关于前端:Proxy详解

14次阅读

共计 9151 个字符,预计需要花费 23 分钟才能阅读完成。

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

下面这段代码中,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 = 100
console.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);  // 100
revoke()  // 勾销 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 : 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 哟

正文完
 0