当前位置: 首页 > 工具软件 > Reflection.js > 使用案例 >

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

白赞
2023-12-01

在实际开发中经常会遇到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()
hasin操作Reflect.has()
deletePropertydelete操作符Reflect.deleteProperty()
getAPrototypeofObject.getAPrototypeof ()Reflect.getAPrototypeof ()
setAPrototypeofObject.setAPrototypeof ()Reflect.setAPrototypeof ()
isExtensibleObject.isExtensible()Reflect.isExtensible()
preventExtensionsObject.preventExtensions()Reflect.preventExtensions()
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor()Reflect.getOwnPropertyDescriptor()
defineaPropertyObject.defineaProperty()Reflect.defineaProperty()
ownKeysObject.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 操作发生的对象(通常是代理)

结合上面3、4 的操作可以封装一个带自定义检测的对象,为以后自己写插件打下基础


	let originObj = {
		employee:'jack'
	};
	
	let optObj = new Proxy(originObj,{
		set(trapTarget,key,value,receiver){
			// 忽略原有属性
			if(!trapTarget.hasOwnProperty(key)){
				if(!isNaN(value)){ // 属性不能位数字
					throw new TypeError('属性不能为数字!')
				}
			}
			// 添加属性
			return Reflect.set(trapTarget,key,value,receiver);
		},
		get(trapTarget,key,receiver){
			if(!(key in receiver)){
				throw new TypeError('属性'+key+'不存在');
			}
			
			return Reflect.get(trapTarget,key,receiver);
		}
	});
			

用node测试哈


	> optObj.sex = 'female';
	'female'
	> console.log(optObj.sex);
	female
	undefined
	> console.log(originObj.sex);
	female
	undefined
	> optObj.age =18;
	Thrown:
	TypeError: 属性不能为数字!
	    at Object.set (repl:6:14)
	> console.log(optObj.age);
	Thrown:
	TypeError: 属性age不存在
	    at Object.get (repl:14:13)
	> console.log(optObj.name)
	Thrown:
	TypeError: 属性name不存在
	    at Object.get (repl:14:13)
	> console.log(optObj.employee)
	jack
	

五、函数代理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先到这里,时间不早了改且休息了;改天继续…

 类似资料: