宝哥软件园

你知道如何实现vue3.0响应数据吗

编辑:宝哥软件园 来源:互联网 时间:2021-08-22

从代理开始

什么是代理

由代理人翻译,意思是“代理人”。ES6对代理的定位是在目标对象(原对象)的基础上,通过处理程序增加一层“拦截”,并返回一个新的代理对象。之后,代理中所有被拦截的属性都可以通过一些新的流程进行定制。首先,看一个最简单的例子。

const target={ };//要代理的原始对象//用于描述代理进程的handler const handler={ get: function(target,key,receiver){ console . log(` get $ { key }!`);return Reflect.get(目标、键、接收器);},set:函数(目标、键、值、接收器){ console.log(`setting ${key}!`);返回Reflect.set(目标、键、值、接收器);}}//obj是新的代理对象constobj=newproxy(目标,处理程序);obj.a=1 //设置a!console.log(obj.a) //正在获取!在上面的例子中,我们在目标对象上设置了一层处理程序,它拦截目标的get和set,然后我们可以在get和set之间做一些额外的操作

注1:Proxy对象的赋值操作也会影响原对象目标,目标的操作也会影响Proxy,但直接操作原对象不会触发拦截的内容~

obj . a=1;//设置a!Console.log(target.a) //1不会打印“get a!”注意2:如果在处理程序中没有对拦截器的处理,对代理对象的操作将直接导致原始对象

const target={ };const处理程序={ };const obj=new Proxy(目标,处理程序);obj . a=1;Console.log(target.a) //1因为代理也是一个对象,所以可以作为原型对象使用。因此,在将obj的原型指向代理之后,我们发现obj的操作会在原型上找到代理对象。如果obj有自己的A属性,就不会触发代理上的get,这一点应该很好理解。

const target={ };const obj={ };const handler={ get : function(target,key){ console . log(` get $ { key } from $ { JSON . stringify(target)} `);return Reflect.get(target,key);}}const proxy=new Proxy(目标,处理程序);object . set rototypeof(obj,proxy);proxy . a=1;obj . b=1 console . log(obj . a)//从{ ' a ' : 1 } 1 console . log(obj . b)//1e S6获取的Proxy截取了哪些属性?

通过上面的例子了解了Proxy的原理之后,我们再来看看ES6拦截了哪些属性,它们分别能做什么。以下是Proxy支持的13种拦截操作的列表

Get(target,propKey,receiver):拦截对象属性的读取,如proxy.foo和proxy[' foo '];Set (target,propkey,value,receiver):截取对象属性的设置,如proxy.foo=v或proxy['foo']=v,返回布尔值;Has(target,propKey):截获propKey在代理中的操作并返回一个布尔值。DeleteProperty(target,propKey):拦截删除代理[propKey]的操作并返回布尔值;OwnKeys(目标):截取对象的循环。getownpropertynames(代理),对象。getownpropertysymbols(代理),Object.keys(代理),for…in,并返回一个数组。方法返回目标对象所有属性的属性名,而Object.keys()的返回结果只包括目标对象本身的遍历属性。Getownpropertydescriptor(目标,propkey):截取对象。getowntpropertysdescriptor(proxy,propkey)并返回属性的description对象;定义属性(目标,道具键,道具desc):拦截对象。定义属性(代理、道具密钥、道具desc)和对象。定义属性(代理、道具desc)并返回布尔值;PreventExtensions(目标):拦截对象。防止扩展(代理)并返回布尔值;GetPrototypeOf(目标):截取Object.getPrototypeOf(代理)并返回一个对象;IsExtensible(目标):截取Object.isExtensible(代理)并返回一个布尔值;SetPrototypeOf(target,proto):截取object.setprototypeof (proxy,proto)并返回一个布尔值。如果目标对象是函数,还有两个额外的操作可以截取;Apply(target,object,args):截取Proxy实例作为函数调用,如proxy(…args),proxy.call(object,…args),Proxy . apply(…);Construct(target,args):截取Proxy实例作为构造函数调用的操作,如new Proxy(…args);以上是目前es6支持的代理,具体用法不再赘述。有兴趣的话可以去阮一峰的es6初级读本学习一下每一本的具体用法。其实思路是一样的,只是每一个对应的功能有些不同~

Proxy在真实场景中能做什么?

实现私有变量

js语法中没有private关键字来修改私有变量,所以基本上类的所有属性都可以访问,但是在某些场景中我们需要使用私有变量。现在业内一些做法用“_变量名”来“规定这是私有变量,但是如果有一天有人从外面改了,我们还是阻止不了。但是,当代理出现时,我们可以使用代理来处理这种情况。

const obj={ _name: 'nanjin ',age: 19,getName: ()={ return this。_ name},setName: (newName)={ this。_ name=newName} } const proxybj=obj=new Proxy(obj,{ get: (target,key)={ if(key . start swith(' _ '))} { throw new Error(`$ { key }是私钥,请使用get $ { key } `)} return reflect . get(target,key);},set: (target,key,new val)={ if(key . startswith(' _ ')){抛出新错误(`${key}是私钥,请使用set${key}`) }返回Reflect.set(target,key,new val);} })const newObj=proxyObj(obj);console.log(newObj。_ name)//uncache error : _ name是私钥,请使用get_namenewObj。_ name=' newname//uncashierror : _ name为privatekey,请使用set _ name console . log(new obj . age)//19 console . log(new obj . getname())//nanjin可以看到,通过proxyObj方法,我们可以将任意对象过滤一次,然后返回新的代理对象。被处理的对象将截取所有以_开头的变量。此外,如果使用过mobx的学生会发现mobx中的商店中的对象与此相似。

有handler和target,这说明mobx本身也使用代理模式,并且添加了decorator函数,这相当于在这里使用proxyObj作为Decorator。proxyobj是mobx的核心原理~

Vue响应数据实施

VUE的双向绑定涉及模板编译、响应数据、订阅者模式等。如果你感兴趣,可以在这里阅读,因为本文的主题是代理,所以我们将重点放在数据响应的过程上。

2.x版本

在当前版本的vue2.x中,在数据中命名了一个obj后,vue会使用Object.defineProperty递归地将get和set添加到数据中的数据中,然后每次设置时都会添加额外的逻辑。要触发相应模板视图的更新,请查看伪代码:

常量定义活动数据=数据={对象.键(数据)。forEach(key={ let value=data[key];object . defineperoperty(data,key,{ get : function(){ console . log(` get $ { key } `)返回值;},set : function(new value){ console . log(` setting $ { key } `)notify()//notify相关模板编译值=newValue},可枚举: true,可配置: true})}}这个方法可以添加get和set到数据上的所有属性。当然,这只是伪代码。在实际场景中,我们还需要考虑,如果一个属性仍然是一个对象,我们应该递归地继续尝试:

const data={ name: ' nanjing ',age : 19 } defineereactivedata(data)data . name//get name ' ningjing ' data . name=' Beijing ';//设置名称可以看到,当我们获取和设置触发器时,可以同时触发我们想要调用的函数。在Vue双向绑定的过程中,在这个上改变数据时更新模板的核心原理就是这个方法,通过这个方法我们可以在设置了数据的某个属性时,触发相应模板的更新。

现在让我们试试下面的代码:

const data={ userids 3360[' 01 ',' 02 ',' 03 ',' 04 ',' 05 ']} defineereactivedata(数据);data.userids//gettinguserids[' 01 ',' 02 ',' 03 ',' 04 ',' 05']的//get过程没有问题。现在我们试着推一个数据data . userids . push(' 06 ')//gettingserids什么?设置没有被触发,但是因为用户标识被取了一次,get ~被触发了一次。

此外,许多数组方法不会触发设置。例如,推、弹出、移位、取消移位、拆分、排序和反转都会改变数组,但不会触发设置。因此,Vue重新打包这些函数来解决这个问题,并在调用这些方法时手动触发notify()。看看源代码:

//获取数组原型const array proto=array。原型导出常量数组方法=对象。create(array proto)//重写以下函数const method stopatch=['push ',' pop ',' shift ',' unshift ',' splice ',' sort ',' reverse ',]methods to atch . foreach(function(method){//cache native function const origin=arrayproto[method]//override function def(arrayMethods,method,The function mutator(.args) {//首先调用本机函数以获取结果const result=original。apply (this,args) const ob=this。_ _ ob _ _ let inserted调用以下函数时,要监视新数据,请切换(方法){ case ' push ' : case ' unshift ' : insert=args break case ' split ' : insert=args。slice(2)break } if(insert)ob . observer array(insert)//Manual dispatch update ob . dep . notify()返回结果})})以上是官方源码。我们可以实现push的伪代码。为了省事,我们直接从原型开始~

const push=array . prototype . push;Array.prototype.push=function(.args){ console.log('push正在发生');返回push.apply(this,args);} data . userids . push(' 123 ')//push正在发生这样,我们可以听听这些变化,但是vue的官方文档中有一个注释。

由于JavaScript的限制,Vue无法检测到以下已更改的数组:

当你使用索引直接设置一个项时,例如:vm.items[indexOfItem]=newValue,当你修改数组的长度时,例如:vm.items.length=newLength,最根本的原因是在这两种情况下,由于js本身无法监控,官方建议使用JS本身提供的内置api。我们也可以理解,这既不能用defineProperty来处理,也不能用一层函数来解决。这是2.x版本中的一个问题,回到本文的主题,vue官方将在3.x版本中使用代理而不是defineProperty来处理响应数据。让我们先模拟一下实现,看看它能否解决目前遇到的这些问题;

3.x版本

我们首先通过代理劫持获取和设置数据对象,并返回一个代理对象。注意,我们只关注代理本身,所有的实现都是伪代码,感兴趣的同学可以提升自己

const defineReactiveProxyData=data=new Proxy(data,{ get: function(data,key){ console . log(` get $ { key } `))返回Reflect.get(data,key);},set:函数(数据、键、NewVal){ console . log(` setting $ { key } `);if(type of NewVal==' object '){//如果是object,递归设置代理returnreflect.set (data,key,definereactiveproxydata(NewVal));}返回Reflect.set(数据、键、new val);} })const data={ name : ' Nanjing ',age : 19 };const VM=defineReactiveProxyData(数据);VM . name//get name nanjingvm . age=20;//设置年龄20看来我们的代理已经工作了。之后,只需要在设置的时候加上notify()通知模板编译,然后我们试着设置一个数组看看;

vm.userIds=[1,2,3] //设置useridsvm . userids . push(1);//获取用户标识,因为我们将首先访问一次用户标识。//gettingpush调用push方法,所以我们将访问push属性一次。//获取长度数组在推送时会发生变化,所以我们需要先访问下标设置的原始长度//设置3。因此,set的当前索引是3//设置长度会改变数组的长度,所以set length//4会返回新数组的长度。回顾2.x遇到的第一个问题,Array.prototype上有些方法需要重新打包,使用代理后就不需要了。~已经解决,继续下一个问题。

虚拟机。userids . length=2//gettingserids首次访问//设置VM前设置长度。userids[1]=' 123 '/gettingserids first access//setting 1 item//with index=1//123 '从上面的例子中,我们可以看到,无论是直接改变数组的长度,还是通过某个下标改变数组的内容,proxy都可以拦截这种变化,这比defineProperty方便多了。2.x版本中的第二个问题根本不会出现在代理中。

摘要1

通过上面的例子和代码,我们可以看到,如果使用代理,Vue的响应模式会比目前的实现模式简化和优化得多,在即将到来的3.0版本中,每个人都可以体验到。但是,因为代理本身是兼容的,比如ie浏览器,所以在低版本场景下vue会回落到当前的实现模式。

摘要2

回到代理本身,在设计模式中有一个典型的代理模式,代理是js的一个实现。它的优点是我可以在不污染自己对象的情况下生成一个新的代理对象,并将所有的目标逻辑放入代理对象中实现,这样我就可以从A对象中派生出B、C、D.每个过程都是不同的,从而简化了代码的复杂性,提高了可读性。例如,使用代理实现数据库的ORM就是一个很好的应用。其实代码很简单。关键是理解背后的想法,同时又能举一反三~

分机:1。Proxy.revocable()

这个方法可以返回一个可取消的代理对象

const obj={ };const处理程序={ };const {proxy,revoke }=proxy . revoke(obj,handler);proxy . a=1 proxy . a//1 revolve();proxy . a//unacchitype error :无法对已撤销的代理执行“get”。一旦代理被取消,就不能再从代理对象访问它

打印代理,您可以看到IsRevoked变为真

2.代理对象的这个问题

因为新的代理作为一个新的对象出现,如果您在目标中使用它,被代理的对象将指向新的代理对象,而不是原始对象。这时,如果某些功能是原对象独有的,就会出现这种指向带来的问题。在这种情况下,建议使用bind强制绑定这个。

看看代码:

常量目标=新日期();const处理程序={ };const proxy=new Proxy(目标,处理程序);proxy . GetDate();//unsighttypeerror :这不是一个日期对象。因为proxy之后的对象不是日期类型,也没有getDate方法,所以我们在获取时需要绑定这一点。

常量目标=新日期();const handler={ get : function(target,key){ if(target[key]的类型==' function '){ return target[key]。bind(target)//strong制将此绑定到原始对象} return reflect。get (target,key)} };const proxy=new Proxy(目标,处理程序);proxy . GetDate();//6这个可以正常使用。当然,具体用途要看具体场景。灵活运用!

以上就是本文的全部内容。希望对大家的学习有帮助,支持我们。

更多资讯
游戏推荐
更多+