js代理(Proxy) 和 反射(Reflection)

2023-08-04

在实际开发中经常会遇到js抛出的错误,但是我们有没有想过自己去接管js异常验证,根据自己的需求抛出异常呢?原本也许不行,但是在es6出来后就可以做到了

一、代理(Proxy)

什么是‘代理’ 呢?代理:就是调用new 创建一个和目标(traget)对象一直的虚拟化对象,然该代理中就可以拦截JavaScript引擎内部目标的底层对象的操作;这些底层操作被拦截后会触发响应特定操作的陷阱函数

来看个简单的案例

    let tartget = {};
let proxy = new Proxy(target,{});
proxy.name = 'proxy';
console.log(proxy.name); // proxy
console.log(tartget .name); // proxy tartget .name = 'tartget';
console.log(proxy.name); // target
console.log(tartget .name); // target

如案例

proxy.name = 'proxy'; 将proxy赋值给proxy.name时,代理就会将该操作转发给目标,执行name属性的创建;然而他只是做转发而不会存储该属性;

so他们之间存在一个相互引用;tartget .name设置一个新值后,proxy.name值也改变了;

二、反射(Reflect)

那反射又是什么呢?反射:它提供了一个Reflect对象API;该对像中的方法默认特性与底层的操作对应;而每一个代理陷阱会对应一个命名和参数都相同的Reflect方法(其实就是每个代理陷阱都会对应一个Reflect api接口供覆写JavaScript底层操作)

映射关系如下表:

代理陷阱 覆写的特性 默认特性
get 读写一个属性值 Reflect.get()
set 写入一个属性 Reflect.set()
has in操作 Reflect.has()
deleteProperty delete操作符 Reflect.deleteProperty()
getAPrototypeof Object.getAPrototypeof () Reflect.getAPrototypeof ()
setAPrototypeof Object.setAPrototypeof () Reflect.setAPrototypeof ()
isExtensible Object.isExtensible() Reflect.isExtensible()
preventExtensions Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineaProperty Object.defineaProperty() Reflect.defineaProperty()
ownKeys Object.keys() 、 Object.getOwnPropertyNames()和 Object.getOwnPropertySysmbols() Reflect.ownKeys()
apply 调用一个函数 Reflect.apply()
construct 用new调用一个函数 Reflect.construct()

三、使用set陷阱验证属性

接下来使用set陷阱来验证一下对象属性赋值操作(如为对象新增属性,要求必须赋值为int)

let target = {
name :'target'
};
let proxy = new Proxy(target,{
set(trapTarget,key,value,receiver){
//忽略不希望受到影响的已有属性
if(!trapTarget.hasOwnProperty(key)){
if(isNaN(value)){
throw new TypeError("属性必须是数字哟,亲!");
}
}
// 添加属性
return Reflect.set(trapTarget,key,value,receiver);
}
}); // 添加一个新属性
proxy.count = 1;
console.log(proxy.count); // 1
console.log(proxy.count); // 1 // 由于目标已有name属性,so 如上第一个if不成立(赋值成功)
proxy.name= "proxy";
console.log(proxy.name); // proxy
console.log(proxy.name); // proxy // 新建一个属性同时赋值一个非int 值,第一个if成立,第二个if验证isNaN(key) = true 即抛出异常
proxy.anotherName = "proxy";

案例中set(trapTarget,key,value,receiver) 这个set陷阱默认接收 四个参数

trapTarget 用于接收属性(代理的目标)的对象
key 要写入的属性键(字符串或Symbol类型)
value 被写入的属性的值
receiver 操作发生的对象(通常是代理)

四、使用get 陷阱验证对象结构

let target = {};
console.log(target.name); // undefined

在JavaScript中调用一个对象不存在的属性不会报错,反而使用undefined代替被读取属性的值

而喝多时候会带来意想不到的bug,现在我们可以使用get陷阱来验证该问题

依然看这个案例

    let proxy = new Proxy(target,{
get(trapTarget,key,receiver){
//忽略不希望受到影响的已有属性
if(!(key in receiver)){
throw new TypeError("sorry 亲! 你找的 "+key+" 属性不存在。!")
}
// 添加属性
return Reflect.get(trapTarget,key,receiver);
}
});
// 添加一个属性,
proxy.name= "proxy";
console.log(proxy.name); // proxy // 读取一个不存在的属性 直接会抛出异常
console.log(proxy.nme);

如上使用in操作判断receiver中是否存在被读取的属性;如果没有抛出异常

其中get(trapTarget,key,receiver) 参数

trapTarget 被读取属性源对象(代理的目标)
key 要读取的属性键(字符串或Symbol类型)
receiver 操作发生的对象(通常是代理)

五、函数代理apply和construct陷阱

使用这个两个陷阱来验证函数调用时的参数的正确性

如下案例

// 参数求和
function sum (...values){
return values.reduce((previous,current) => prvious + current, 0);
} let sumProxy = new Proxy(sum,{
apply:function(trapTarget,thisArg,argumentList){
argumentList.forEach(arg => {
if(typeof arg !== "number"){
throw new TypeError("所有参数必须是数字,亲!");
}
});
return Reflect.apply(trapTarget,thisArg,argumentList);
},
// 防止使用new 关键字调用
construct:function(trapTarget,argumentList){
throw new TypeError("亲,你不能这么干,该函数不能通过new调用。");
}
}); // 测试哈
console.log(sumProxy(1,2,3,4)); // 10 // 传入一个非数字的属性值试试 【直接抛出异常】
console.log(sumProxy(1,“2”,3,4)); // 10 // 同样使用new调用 【直接抛出异常】
let result = new sumProxy();

apply陷阱和Reflect.apply()都接受同样的参数

trapTarget 被执行的函数(代理的目标)
thisArg 函数被调用时内部的this的值
argumentList传递给函数的参数数组

当使用new调用函数时 会触发construct陷阱,接收的参数为

trapTarget 被执行的函数(代理的目标)
argumentList传递给函数的参数数组

其中Reflect.construct()第三个参数是newTarget 这是一个可选参数。用于指定该函数内部

new.target的值

看到这里有没有感觉这个对于js项目代码检测还是蛮有用处的呢。

ok先到这里,时间不早了改且休息了;改天继续…

js代理(Proxy) 和 反射(Reflection)的相关教程结束。