当前位置: 首页 > 后端技术 > Node.js

拿Proxy可以做哪些有意思的事儿

时间:2023-04-03 15:19:35 Node.js

Proxy可以做哪些有趣的事情?可以理解为有一个人气很高的明星开通了微博账号。这个账号很活跃,回复粉丝,到处点赞等等,但不一定是自己维护的。而是背后有另一个人或团队在操作,我们可以称他们为代理人,因为他们发布的微博代表了明星本身的意义。附言强行举个例子,因为我不追星,只是猜测可能有这样的运营团队。把this代入JavaScript可以理解为对象或函数的代理操作。JavaScript中的ProxyProxy是ES6中提供的一个新的API,可以用来定义对象各种基本操作的自定义行为(文档中称为traps,我觉得可以理解为对象各种行为的钩子)拿来就可以做很多有趣的事情,当我们需要控制一些对象的行为时,它会非常有效。代理语法创建一个Proxy实例,需要传入两个参数target被代理,可以是对象也可以是functionhandlers,用来处理对代理对象的各种操作lettarget={}lethandlers={}//donothingletproxy=newProxy(target,handlers)proxy.a=123console.log(target.a)//123基本可以理解为当第二个参数为空对象时对第一个参数的操作浅拷贝(proxy必须是浅拷贝copy,如果是深拷贝,就失去了代理的意义)Traps(各种行为的代理)就像上面的示例代码一样,如果没有定义对应的trap,则没有任何作用,等价直接操作目标。当我们写一个陷阱,当我们做相应的动作时,我们的回调函数就会被触发,我们就会控制代理对象的行为。最常用的两个陷阱应该是get和set。早年的JavaScript在定义对象时,会为某个属性设置getter和setter:letobj={_age:18,getage(){return`I'm${this._age}岁`},setage(val){this._age=Number(val)}}console.log(obj.age)//我18岁obj.age=19console.log(obj.age)//我是19岁像这样在代码部分描述的,我们设置一个属性_age,然后设置一个getage和setage。然后我们可以直接调用obj.age得到一个返回值,或者赋值。这样做有几个缺点:对于每个要代理的属性,必须编写相应的getter和setter。还必须有一个存储真实值的键(如果我们直接在getter中调用this.age,会出现栈溢出,因为每当调用this.age获取值时,都会触发getter)。Proxy很好的解决了这两个问题:,set(target,property,value){target[property]=value}}letproxy=newProxy(target,handlers)proxy.age=19console.log(target.age,proxy.age)//19岁:19console.log(target.name,proxy.name)//NikoBellic,name:NikoBellic我们通过创建get和set两个陷阱来统一管理所有操作。可以看到在修改代理的同时,目标的内容也被修改了,我们对代理的行为做了一些特殊的处理。而我们不需要使用额外的键来存储真正的值,因为我们在陷阱内部操作的是目标对象,而不是代理对象。使用Proxy做什么因为使用了Proxy之后,对象的行为基本上是可控的,所以我们可以用它来做一些以前实现起来比较复杂的事情。下面列举几个简单的适用场景。解决对象属性未定义的问题在一些深层对象属性的获取中,如何处理未定义一直是一个痛苦的过程。如果我们使用Proxy,就可以很好的兼容这种情况。(()=>{lettarget={}lethandlers={get:(target,property)=>{target[property]=(propertyintarget)?target[property]:{}if(typeoftarget[property]==='object'){returnnewProxy(target[property],handlers)}returntarget[property]}}letproxy=newProxy(target,handlers)console.log('z'inproxy.x.y)//false(实际上,这一步已经为`target`创建了一个x.y属性)proxy.x.y.z='hello'console.log('z'inproxy.x.y)//trueconsole.log(target.x.y.z)//hello})()我们代理get,在里面进行逻辑处理。如果我们要获取的值来自一个不存在的key,我们会在target中创建一个对应的key,然后为这个key代理对象返回一个key。这样可以保证我们的value操作不会throwcannotgetxxxfromundefined,但是这样会有一个小缺点,就是如果真的要判断key是否存在,只能通过in来判断operator,not是直接get判断的。普通函数和构造函数的兼容处理如果我们给别人提供一个Class对象,或者ES5版本的构造函数。如果不使用new关键字调用,Class对象会直接抛出异常,ES5中的构造函数this点会成为函数调用时的作用域。我们可以使用applytrap来兼容这种情况:classTest{constructor(a,b){console.log('constructor',a,b)}}//Test(1,2)//throwanerrorletproxyClass=newProxy(Test,{apply(target,thisArg,argumentsList){//如果要禁止使用非new方法调用函数,直接抛出异常即可//thrownewError(`Function${target.name}cannotbeinvokedwithout'new'`)returnnew(target.bind(thisArg,...argumentsList))()}})proxyClass(1,2)//构造函数12我们使用apply代理一些behaviors,函数调用的时候会被触发,因为我们清楚的知道代理是一个Class或者constructor,所以我们直接在apply中使用new关键字来调用代理函数。而如果我们想限制函数,禁止使用new关键字来调用,我们可以使用另一个陷阱:constructfunctionadd(a,b){returna+b}letproxy=newProxy(add,{construct(target,argumentsList,newTarget){thrownewError(`Function${target.name}cannotbeinvokedwith'new'`)}})proxy(1,2)//3newproxy(1,2)//抛出错误Proxy用于包装fetch在前端发送请求。我们现在经常用到的应该是fetch,一个原生提供的API。我们可以用Proxy包裹起来,方便使用。lethandlers={get(target,property){if(!target.init){//初始化对象['GET','POST'].forEach(method=>{target[method]=(url,params={})=>{returnfetch(url,{headers:{'content-type':'application/json'},mode:'cors',credentials:'same-origin',method,...params}).然后(response=>response.json())}})}returntarget[property]}}letAPI=newProxy({},handlers)awaitAPI.GET('XXX')awaitAPI.POST('XXX',{body:JSON.stringify({name:1})})封装了GET和POST,可以直接通过.GET调用,设置一些常用参数。实现一个简单的断言工具所有写过测试的孩子都应该知道断言。console.assert是一个接受两个参数的断言工具。如果第一个为false,则第二个参数将作为错误消息抛出。出去。我们可以使用Proxy做一个工具,可以实现直接赋值断言。letassert=newProxy({},{set(target,message,value){if(!value)console.error(message)}})assert['Isn\'ttrue']=false//错误:是'ttrueassert['Lessthan18']=18>=19//错误:小于18统计函数调用次数在做服务端的时候,我们可以使用Proxy代理一些函数来统计一段时间内的调用次数的时间。可能对后面的性能分析有用:陷阱这里是处理程序的所有可定义行为(陷阱)的列表:详情可以查看MDN-Proxy。traps中也有一些例子descriptionget获取某个key值set设置某个key值has使用in运算符判断某个key是否有apply函数调用,仅当代理对象为函数时有效。ownKeys通过实例化调用获取目标对象的所有keyconstruct函数,仅在代理对象为函数时有效。isExtensible判断对象是否可扩展,代理对象的deleteProperty。DeleteapropertydefinePropertyDefineanewpropertygetPrototypeOf获取原型对象setPrototypeOf设置原型对象preventExtensions设置对象不可扩展getOwnPropertyDescriptor获取自身属性的属性描述(不搜索原型链)ReferenceMagicMethodsinJavaScript?认识代理!如何使用JavaScript代理获得乐趣和收益MDN-Proxy