产品经理。需求的变化是不可避免的。测试姐眯着眼睛笑道,今晚的bug肯定不少。据悉,Vue3.0正式版将于本月(8月)发布。从发布到正式项目,还有一定的过渡期,但总不能等到Vue3正式投入到项目中再学习吧,提前学习,让你更快掌握Vue3.0一步,升职加薪靠它嫁给白富美。但是,在学习Vue3之前,你需要了解一下Proxy,Proxy是Vue3.0实现双向数据绑定的基础。本文是作者Vue3.0系列的第一篇文章,作者每周都会发布一篇关于Vue3.0的文章。如果喜欢,请给小编点个赞。感谢您了解代理模式。举个单身钢铁直男程序员的例子,最近小王渐渐喜欢上了前台妹子,但对前台妹子并不熟悉,于是决定委托UI小姐姐,UI小姐姐更熟悉前端的妹子,帮他架起一座桥梁。于是小王请了UI小姐姐吃了一顿大餐,然后拿出一封情书托付给了前台小姐姐。情书说喜欢你,想睡你,不愧是钢铁直男。不过这样写也没用,UI小姐姐嘴巴短,帮着把情书改成了我喜欢你,想和你一起在辰辉的澡下醒来,然后递给了那个女孩前台。虽然婚介是否成功不得而知,但这个故事告诉我们,小王活该单身。其实以上就是代理模式的一个比较典型的例子。小王想给前台姑娘送情书,但因为不熟,便委托了相当于代理人的UI小姐,代小王完成了情书的投递。扩展上面的例子,我们来思考一下Vue的数据响应原理,比如下面的代码constxiaowang={love:'我喜欢你,我想和你一起睡'}//给姐姐发情书functionsendToMyLove(obj){console.log(obj.love)return'流氓,迷路'}console.log(sendToMyLove(xiaowang))如果没有UI小姐姐而不是发情书,就说明结局悲剧了。想想Vue2.0的双向绑定,双向绑定是通过Object.defineProperty监控的属性get和set方法实现的。这个Object.defineProperty相当于UI小姐姐constxiaowang={loveLetter:'我喜欢你,我想和你一起睡'}//UI小姐姐AgentObject.defineProperty(xiaowang,'love',{get(){returnxiaowang.loveLetter.replace('Sleep','沐浴牵牛花')}})//给小姐姐写一封情书functionsendToMyLove(obj){console.log(obj.love)return'小伙子挺有诗意的,不过我不喜欢,滚出去'}console.log(sendToMyLove(xiaowang))虽然还是个悲剧故事,因为派奔驰可能成功率更高。但是我们可以看到Object.defineproperty可以拦截对象已有的属性,然后再做一些额外的操作。存在的问题在Vue2.0中,双向数据绑定是通过Object.defineProperty监听对象的各个属性,然后在get和set方法中通过发布订阅者方式实现数据响应,但是存在一定的缺陷。例如,它只能监视现有的属性,而不能对属性的添加和删除做任何事情。同时不能监听数组的变化,所以在Vue3.0中被更强大的Proxy取代。理解ProxyProxy是ES6引入的一个新特性,可以用来拦截js的操作方法,从而对这些方法进行代理操作。用Proxy重写上面的例子比如我们可以通过Proxy重写上面的情书情节:target,key){if(key==='loveLetter'){returntarget[key].replace('Sleep','一起在朝霞下醒来')}}})//送给小姐姐LoveletterfunctionsendToMyLove(obj){console.log(obj.loveLetter)return'小伙子是不是挺有诗意的,老婆不喜欢,滚'}console.log(sendToMyLove(proxy))请用分别看这样的场景,Object.defineProperty和Proxy完成如下代码逻辑。functionobserve(obj,callback){}constobj=observe({name:'子君',sex:'男'},(key,value)=>{console.log(`属性的值[${key}]改为[${value}]`)})//这段代码执行后,输出属性[name]的值改为[sisterpaper]obj.name='SisterPaper'//之后这段代码执行后,输出属性[sex]的值改为[female]obj.sex='female'看完上面的代码,希望你能自己体会下面的内容。接下来,我们将分别使用Object.defineProperty和Proxy来实现上述逻辑。UseObject.defineProperty/***请实现此函数,使以下代码逻辑正常运行*@param{*}objobject*@param{*}callback回调函数*/functionobserve(obj,callback){constnewObj={}Object.keys(obj).forEach(key=>{Object.defineProperty(newObj,key,{configurable:true,enumerable:true,get(){returnobj[key]},//当修改属性的值时,会调用set,此时可以回调函数set(newVal)在集合中调用{obj[key]=newValcallback(key,newVal)}})})returnnewObj}constobj=observe({name:'子君',sex:'Male'},(key,value)=>{console.log(`属性[${key}]的值更改为[${value}]`)})//这段代码执行后,输出属性[name]的值发生了变化to[sisterpaper]obj.name='SisterPaper'//这段代码执行后输出属性[sex]的值变为[female]obj.name='female'useProxyfunctionobserve(obj,callback){returnnewProxy(obj,{get(target,key){returntarget[key]},set(target,key,value){target[key]=valuecallback(key,value)}})}constobj=observe({name:'子君',sex:'男'},(key,value)=>{console.log(`属性[${key}]的值更改为[${value}]`)})//后这段代码执行后,输出属性[name]的值变为[girl]obj.name='girl'//这段代码执行后,输出属性[sex]的值变为[female]obj.name='female'通过以上两种不同的实现方式,我们可以大致了解Object.defineProperty和Proxy的用法,但是在给对象添加新的属性时,区别就出来了。例如//add公众号fieldobj.gzh='somefunatthefrontend'新的属性不能使用Object.defineProperty来监听,但是可以使用Proxy来监听对比上面两段代码,我们可以发现如下区别Object.defineProperty监控对象的每一个属性,而Proxy监控对象本身。使用Object.defineProperty需要遍历对象的每一个属性,对性能会有一定的影响。Proxy也可以监听新的属性,但是Object.defineProperty不能监听。认识Proxy的概念和语法在MDN中,Proxy的介绍是这样的:Proxy对象用于定义基本操作(如属性查找、赋值、枚举、函数调用等)的自定义行为。这意味着什么?代理就像一个拦截器。它可以在读取对象的属性、修改对象的属性、获取对象属性列表、通过forin循环等操作时拦截对象上的默认行为,然后自己定制。这些行为,比如上面例子中的set,我们拦截默认的set,然后在自定义的set中添加一个回调函数来调用Proxy。语法格式如下/***target:要兼容的对象,可以是对象,数组,函数等。*handler:是一个对象,包含了可以监控这个对象的行为函数,比如`get`而上面例子中的`set`*会同时返回一个新的对象proxy,为了能够触发handler里面的函数必须使用返回值来进行其他操作,比如修改值*/constproxy=newProxy(target,handler)在上面的例子中,我们已经使用了handler中提供的get和set方法,接下来我们就来一一了解一下handler中的方法。handler中的方法列表handler中的方法可以有以下十三个方法,每个方法对应一个或多个对proxy代理对象handler的操作行为。get通过proxy读取对象中的属性时,会进入get钩子函数中的handler.set。通过代理为对象设置和修改属性时,会进入sethook函数中的handler.has。当使用in判断property是否在proxy代理对象中时,会触发has,如constobj={name:'子君'}console.log('name'inobj)handler.deleteProperty当使用delete删除时对象中的属性,会进入deleteProperty`钩子函数handler.apply,当proxy监听一个函数时,会进入apply钩子函数handle.ownKeys。当通过Object.getOwnPropertyNames、Object.getownPropertySymbols、Object.keys、Reflect.ownKeys获取对象信息时,会进入ownKeyshook函数handler.construct使用new操作符时,会进入construct的handler函数handler.defineProperty。使用Object.defineProperty修改属性修饰符时,会在读取对象原型时进入handler函数handler.getPrototypeOf。,会进入这个钩子函数handler.setPrototypeOf设置对象的原型时,会进入这个钩子函数handler.isExtensible判断对象是否可以通过Object.isExtensible添加新属性时,进入这个钩子函数handler.preventExtensions当使用时Object.preventExtensions设置对象不能修改新的属性,进入这个钩子函数handler.getOwnPropertyDescriptor在获取代理对象某个属性的属性描述时触发这个操作,比如执行Object.getOwnPropertyDescriptor(proxy,"foo")将输入此一个钩子函数Proxy提供了十三种拦截对象操作的方法。本文主要选取其中一些在Vue3中比较重要的进行讲解。其余的建议可以直接看MDN上Proxy的介绍。详细介绍get。通过代理读取对象中的属性时,会进入get钩子函数。当我们从代理读取属性时,会触发get钩子函数。get函数的结构如下/***target:目标对象,即proxy代理的对象*key:要访问的属性名*receiver:receiver相当于属性的this我们要看,一般*就是代理对象本身,关于接收者的作用,后面的文章会详细讲解*/handle.get(target,key,receiver)的例子。我们在工作中经常会有封装axios的需求。在封装过程中,我们还需要对请求异常进行封装,比如返回不同的状态码异常信息不同,下面是部分状态码及其提示信息://状态码提示信息consterrorMessage={400:'Badrequest',401:'系统未授权,请重新登录',403:'RefusedAccess',404:'请求失败,未找到资源'}//如何使用constcode=404constmessage=errorMessage[code]console.log(message)但是有个问题,状态码有很多,我们不能把每一个状态码都枚举出来,所以对于一些异常的状态码,我们希望能够统一提示。如果提示是系统异常,请联系管理员。这时候可以使用Proxy代理错误信息//状态码提示信息consterrorMessage={400:'Badrequest',401:'系统未授权,请重新登录',403:'拒绝访问',404:'请求失败,未找到资源'}constproxy=newProxy(errorMessage,{get(target,key){constvalue=target[key]returnvalue||'系统异常,请联系administrator'}})//输出错误请求console.log(proxy[400])//输出系统异常,请联系管理员。console.log(proxy[500])set给对象中的属性赋值时会触发set。set在为对象中的属性赋值时会触发s。et和set函数结构如下/***target:目标对象,即proxy代理的对象*key:要赋值的属性名*value:要赋给target的新值attribute*receiver:和get*的receiver基本一样/handle.set(target,key,value,receiver)Example一个系统需要输入一系列的值进行数据统计,但是在输入值的时候,可能会有输入了一些异常值,输入时需要对这些异常值进行处理,比如大于100的值转为100,小于0的值转为0。此时,代理集可用于在赋值时处理数据。constnumbers=[]constproxy=newProxy(numbers,{set(target,key,value){if(value<0){value=0}elseif(value>100){value=100}目标[key]=value//对于set,如果操作成功必须返回true,否则视为失败返回true}})proxy.push(1)proxy.push(101)proxy.push(-10)//output[1,100,0]console.log(numbers)与Vue2.0对比在使用Vue2.0时,如果给一个对象添加新的属性,往往需要调用$set。这是因为Object.defineProperty只能监控已有的属性,无法监控新的属性,而通过$set相当于手动给对象添加属性,然后触发数据响应但是对于Vue3.0,因为使用了Proxy,在其sethook函数中可以监听新的属性,所以不再需要使用$setconstobj={name:'子君'}constproxy=newProxy(obj,{set(target,key,value){if(!target.hasOwnProperty(key)){console.log(`新属性${key},值为${value}`)}target[key]=valuereturntrue}})//添加公众号属性//输出增加了一个属性gzh,取值为前端某玩proxy.gzh='前端某玩'在使用in判断属性是否为当在proxy代理对象内部时,会触发has/***target:目标对象,即代理所代理的对象*key:要判断的key是否在target中*/handle.has(target,key)一般情况下的例子当我们在js中声明私有属性时,属性的名称会以_开头。对于这些私有属性,不需要外部调用,所以最好能隐藏起来。这时候可以通过has判断某个属性是否在对象中,如果以_开头则返回falseconstobj={publicMethod(){},_privateMethod(){}}constproxy=newProxy(obj,{has(target,key){if(key.startsWith('_')){returnfalse}returnReflect.get(target,key)}})//输出falseconsole.log('_privateMethod'inproxy)//输出trueconsole.log('publicMethod'inproxy)deleteProperty当使用delete删除对象中的属性时,会进入deleteProperty`拦截器/***target:目标对象,即被代理的对象proxy*key:要删除的属性*/handle.deleteProperty(target,key)的例子现在有一个用户信息的对象。对于某些用户信息,只允许查看,不能删除或修改。为此,使用Proxy可以拦截并抛出无法删除或修改的属性的异常。如下constuserInfo={name:'子君',gzh:'前端玩的东西',sex:'男',age:22}//只有用户名和公众号constreadonlyKeys=['name','gzh']constproxy=newProxy(userInfo,{set(target,key,value){if(readonlyKeys.includes(key)){thrownewError(`Property${key}cannotbemodified`)}target[key]=valuereturntrue},deleteProperty(target,key){if(readonlyKeys.includes(key)){thrownewError(`Property${key}cannotbedeleted`)return}deletetarget[key]returntrue}})//errordeleteproxy.name对比Vue2.0其实和$set解决的问题类似。Vue2.0无法监听到属性被删除,所以提供了$delete来删除属性,但是对于Proxy来说,可以监听删除操作,所以不需要使用$delete其他操作在上面我们提到了Proxy的处理程序提供了十三个函数。上面我们列出了三个最常用的函数。其实各个的用法基本相同,比如ownKeys,当通过Object.getOwnPropertyNames、Object.getownPropertySymbols、Object.keys、Reflect.ownKeys获取对象的信息时,就会进入ownKeys的钩子函数。使用它,我们可以将它用于一些我们不喜欢的暴露的属性。比如一般认为_开头的属性是私有属性,那么在使用Object.keys获取对象的所有key时,可以屏蔽所有_开头的属性。关于剩下的属性,我建议你多看看MDN里面的介绍。反映在上面,我们通过直接操作target来获取属性的值或者修改属性的值,但实际上ES6已经为我们提供了调用Proxy内部对象默认行为的API,即,反映。比如下面的代码constobj={}constproxy=newProxy(obj,{get(target,key,receiver){returnReflect.get(target,key,receiver)}})你可能会看到上面的代码和direct和target[key]的使用方式没有区别,但其实Reflect的出现是为了让对Object的操作更加规范。比如我们要判断某个prop是否在一个对象中,我们通常使用in,即constobj={name:'子君'}console.log('name'inobj)但是上面的操作是命令式语法,通过Reflect可以转化为函数式语法,更加规范Reflect.has(obj,'name')除了has和get,其实Reflect一共提供了十三个静态方法。这十三个静态方法与Proxyhandler上的十三个方法是一一对应的。通过Proxy和ReflectCombined的结合,可以拦截对对象的默认操作。当然,这也属于函数式元编程的范畴。综上所述,可能有同学会有疑惑,不会Proxy和Reflect是不是就不能学Vue3.0了?其实了解这些并不影响学习Vue3.0,但是想要深入了解Vue3.0,了解这些还是很有必要的。比如在使用Vue2的时候经常有人问,为什么我通过索引修改数组的值后界面没有变化?当你了解了Object.defineProperty的用法和局限性后,你就会恍然大悟原来是这么回事。看完这篇,小编将为大家带来Vue3.0系列文章,欢迎大家关注,一起学习。同时
