Proxy,可以理解为在对目标对象操作之前,进行一次拦截,外界对于该对象的操作,必须经过Proxy,为目标对象提供了一种类似于“保护”的机制,Proxy可以对外界的操作可以进行过滤和修改。
const proxyObj = new Proxy({},{
get:function(target, propKey){
console.log('这里是获取值时候的拦截')
return target[propKey]
},
set:function(target,propKey,value){
console.log('这里是设置的时候的拦截')
targe[propKey] = value;
return target
}
})
Proxy 接受两个参数,第一个参数是所代理的对象,就是原本要被操作的对象;第二个参数为配置对象,其中有13种对应的操作拦截。
这里补充一下,我们创建对象实例的时候,new的是代理之后的对象,在上面的例子中就是proxyObj(原对象的参数),参数还是赋值的原对象(目标对象)的属性。那么这个实例的方法与属性和哪个对象有关呢?记住了,是原对象(代理的目标对象)的方法和属性。和这13种拦截操作没有任何的关系。
以下是13种拦截操作:(看以下操作的时候,需要你提前了解一下JavaScript的面向对象编程,根据我看过的小红书以及相关的ES6中class语法糖,写了一篇相关的文章)
- get(target, propKey, receiver):拦截对象属性的读取。
- set(target, propKey, value, receiver):拦截对象属性的设置,返回一个布尔值。
- apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
- has(target, propKey):拦截propKey in proxy的操作,返回一个布尔值。
- construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
- deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回一个布尔值。
- ownKeys(target):拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
- getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
- defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
- preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值。
- getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象。
- isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值。
- setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
get(target,propKey[,receiver])
拦截读取的操作,接受三个参数,依次是目标对象,属性名和proxy本身。
// 拦截读取操作
let Person = {
name:'张三',
age:26
}
let personProxy = new Proxy(Person,{
get:(target,propKey)=>{
if(propKey in target){
return target[propKey]
}else{
return new ReferenceError("属性 "+propKey+" 不存在!")
}
}
})
personProxy.sex //ReferenceError: 属性 sex 不存在!
personProxy.name // 张三
set(target, propKey, value[, receiver])
set方法用来拦截属性的赋值操作,接收四个参数,依次是目标对象、属性名、属性值和Proxy实例本身。
//这段代码实现了对数据的校验
let validator = {
set: function (target, propKey, value) {
if(propKey in target && propKey[0] === '_'){
throw new RangeError('该属性不允许修改!')
}
if (propKey === 'age') {
if (!Number.isInteger(value)) {
throw new RangeError('请输入整数!')
}
if(value > 200 || value < 1){
throw new RangeError('请输入合理的年龄!')
}
target[propKey] = value
}
}
}
let person = new Proxy({_name:'这里是'},validator);
let person = new Proxy({},validator);
person.age = 201
person.age = -10 // Uncaught RangeError: 请输入合理的年龄!
person.age = 1.23 // Uncaught RangeError: 请输入整数!
person._name = 'asd' // Uncaught RangeError: 该属性不允许修改!
vue3.0上重构的双向绑定就是通过proxy实现的。
需要注意的是,proxy代理器只是在原本操作的对象外面再次包装了一层,对于原本的目标对象属性中不可以修改的,set是不会 起作用的。
apply(target, object, args)
主要拦截的是函数的调用、call和apply操作。接收三个参数,依次是目标对象、目标对象的上下文和目标对象参数数组。
let handle = {
apply:(target,obj,args)=>{
return args[0] - args[1]
}
}
let fn = function(x,y){return x+y}
let proxyFn = new Proxy(fn,handle);
proxyFn(1,2)
proxyFn(10,2)
proxyFn.call(null,10,1)
proxyFn.apply(null,[10,1])
has(target, propKey)
has 方法主要是拦截HasPrototype操作,判断某个属性是否属于对象。典型操作就是 in 操作符。
has 方法主要接收两个参数,分别是目标对象、需要查询的属性名。
需要注意的地方就是has方法拦截的是HasPrototype操作,而不是HasOwnPrototype操作,所以has()方法不判断属性是对象自身的,还是继承下来的。同事需要注意的地方还有has 方法对于 for...in 循环不生效。
写个小例子看一下:
let person1 = { name:'Tom',age:20,skill:'cook'}
let person2 = { name:'李梅',age:10}
let proxyHandler = {
has:(target,propKey)=>{
if(propKey == 'skill' && propKey in target){
if(['cook'].includes(target[propKey])){
console.log('这是个有技能的人,而且是厨房里面的技能')
return true
}else{
console.log('这是个有技能的人,但不是厨房里面的技能')
return true
}
}else{
console.log('这是个没有技能的人')
return false
}
}
}
let proxyPerson1 = new Proxy(person1,proxyHandler)
let proxyPerson2 = new Proxy(person2,proxyHandler)
console.log('skill' in proxyPerson1) // 这是个有技能的人,而且是厨房里面的技能 true
console.log('skill' in proxyPerson2) // 这是个没有技能的人 false
construct(target, args)
从名字上看,应该就猜出来是和构造函数有关的,和构造函数有关的操作符那就只是new了。没错,它就是用来拦截new操作符。它可以接收两个参数:依次是目标对象、构造函数的参数数组。
需要注意的是:1、construct方法必须返回一个对象,否则会报错。2、construct方法中的this指向的是proxy第二个参数本身。
const proxy = new Proxy(function () {}, {
construct: function(target, args) {
return { argsToxx:args.map(e=>{return e**2})}
}
});
console.log((new proxy(1,2,3,4,5,6)).argsToxx) //[ 1, 4, 9, 16, 25, 36 ]
deleteProperty(target, propKey)
很明显这是delete操作符的,返回false,则propKey这个属性无法被删除。例子不写了,很简单。
ownKeys(target)
从字面意思来看是属性自身的一些属性,猜想可能是拦截读取部分属性的时操作符。这个确实是拦截自身属性的读取操作:主要是以下四个操作:
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- for...in操作
注意点:1、使用Objetc.keys()方法的时候,有三类属性会被自动过滤(a. 目标对象不存在的属性;b. 属性名为Symbol值;c. 不可遍历(enumberable为false)的属性)。
getOwnPropertyDescriptor(target, propKey)
getOwnPropertyDescriptor()方法拦截Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者undefined。
defineProperty(target, propKey, propDesc)
defineProperty()
方法拦截了Object.
defineProperty()
操作。
getPrototypeOf(target)
主要拦截的是获取对象原型。主要是以下操作:
- Object.prototype.__proto__
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
isExtensible(target)
这个方法主要拦截的是Object.isExtensible()方法。
setPrototypeOf(target, proto)
主要用来拦截Object.
setPrototypeOf()
方法。