手机版

为什么Vue3.0使用代理来监控数据(定义属性意味着不支持这个锅)

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

引导阅读

在vue3.0中,响应数据部分放弃了Object.defineProperty,而使用了Proxy来代替它。本文主要从以下几个方面来分析vue为什么选择放弃Object.defineProperty。

Object.defineProperty真的不能监控数组下标的变化吗?分析vue2.x中的array Observe的源代码,比较Object.defineProperty和Proxy。我不能监控数组下标的变化?

我在一些技术博客上看到过这样一句话,Object.defineProperty有一个缺陷,它不能监控数组的变化:

无法监控数组索引的变化,导致直接通过数组的索引设置数组的值,无法实时响应。为此,vue设置了突变数组的七种破解方法(推送、弹出、移位、解移位、拼接、排序、反向)来解决问题。

Object.defineProperty的第一个缺陷是不能监听数组的变化。但是,Vue的文档中提到Vue可以检测数组变化,但是只有以下八种方法,vm.items[indexOfItem]=newValue无法检测。

这个说法是有问题的。实际上,Object.defineProperty本身可以监控数组下标的变化,但是在Vue的实现中,由于性能/体验的性价比,这个特性被放弃了。

让我们用一个例子来纠正对象的名称。

function defineReactive(数据、键、值){ Object.defineProperty(数据、键、{ enumerable: true,configurable: true,get : function defineGet(){ console . log(` get key : $ { key } value : $ { value } `)返回值},set : function defineSet(newVal){ console . log(` set key : $ { key } value 3: $ { newforEach(function(key){ defineReactive(data,key,data[key]))})让arr=[1,2,3]观察(arr)上面的代码通过Object.defineProperty劫持数组arr的每个属性让我们操作数组arr,看看哪些行为会触发数组的getter和setter方法。

1.通过下标获取元素并修改元素的值

可以看出,通过下标获取元素会触发getter方法,设置某个值会触发setter

方法。

接下来我们再来试试数组的一些操作方法,看看是否会触发。

2.数组的推送方法

Push不触发setter和getter方法,数组的下标可以看作对象中的键。在这里,push之后,相当于添加了一个索引较低的元素3,但是新的下标没有被观察到,所以不会被触发。

3.阵列的非偏移方法

妈的,发生什么事了?

Unshift操作会导致0、1、2、3的原始索引值发生变化,所以需要取出0、1、2、3的原始索引值,然后重新赋值,这样在取值的过程中触发getter,赋值的时候触发setter。

让我们尝试通过索引获取相应的元素:

只有索引为0、1、2的属性才会触发getter。

在这里,我们可以比较对象。arr数组的初始值为[1,2,3],也就是只对索引0,1,2实现observe方法,所以不管以后数组长度如何变化,只有索引为0,1,2的元素发生变化才会触发。其他新增加的索引相当于对象中新增加的属性,需要手动观察。

4.数组pop方法

当被移除的元素是引用2的元素时,Getter被触发。

删除索引为2的元素并修改或获取其值时,Setter和getter不会被触发。

这与对象处理相同。删除数组的索引后,相当于删除了对象的属性,不会再触发observe。

在这里,我们可以简单总结一下结论。

数组中Object.defineProperty的表达式与对象中的一致,数组的索引可以看作对象中的键。

当通过索引访问或设置相应元素的值时,可以通过push或unshift触发getter和setter方法来增加索引。对于新添加的属性,需要手动初始化才能观察到。通过pop或shift删除元素会删除和更新索引,并触发setter和getter方法。因此,Object.defineProperty有能力监控数组下标的变化,但是vue2.x放弃了这个特性。

第二,vue做了什么处理来观察阵列?

vue的观察者类在core/observer/index.js中定义。

如您所见,vue的观察者单独处理数组。

HasProto判断数组的实例是否有__proto__属性。如果有一个__proto__属性,它将执行proteoextense方法,并将arrayMethods重写到原型中。HasProto的定义如下。

ArrayMethods重写了数组方法,并在core/observer/array.js中进行了定义,下面是对这部分源代码的分析。

/* *未对该文件进行类型检查,因为flow不能很好地处理数组原型*/import {def} from '上的*动态访问方法./util/index//复制数组构造函数的原型,Array.prototype也是一个数组。const arrayProto=Array.prototype//创建了一个对象,并且该对象的__proto__指向arrayProto,因此arrayMethods的__proto__包含该数组的所有方法。导出常量数组方法=对象。create(arrayproto)//下面的数组是要重写的方法const方法stopatch=['push ',' pop ',' shift ',' unshift ',' splice ',' sort ',' Reverse']/* * *截取突变方法并发出事件*///遍历method stopatch数组。重写其中的方法。methodstopatch。foreach(function(method){//cache original method const origin=arrayproto[method]//def method在lang.js文件中定义,属性由object.defineProperty重新定义.//也就是在arrayMethods中找到我们要重写的方法,重新定义。def (arraymethods,method,function mutator(.args) {const结果=原始。apply (this,Args) const ob=this。_ _ ob _ _ let插入的开关(方法){//上面已经分析过,对于push,unshift将添加一个索引。因此,需要手动观察case ' push ' : case ' unshift ' : insert=args peak//split方法。如果传入第三个参数,将会有一个新的索引。所以还需要手动观察case ' split ' : insert=args . slice(2)break }//push,unshift,split,然后在这里手动观察,其他方法的变化会在当前索引上更新。因此,如果(插入)ob . observer array(插入)//notifychangeob.dep.notify()返回result})})三个Object.defineProperty VS Proxy,则不需要执行ob . observer array。

上面已经知道Object.defineProperty对于数组和对象表现出相同的性能,那么它与Proxy相比有什么优缺点呢?

1.Object.defineProperty只能劫持对象的属性,Proxy是直接代理对象。

因为对象定义属性只能劫持属性,所以有必要遍历对象的每个属性。如果属性值也是一个对象,就需要深度遍历。而Proxy直接表示对象,不需要遍历。

2.对象定义属性需要手动观察新添加的属性。

由于对象定义属性劫持了对象的属性,当添加属性时,有必要再次遍历对象,向其添加属性,然后用对象定义属性劫持它

因此,当使用vue向数据中的数组或对象添加属性时,有必要使用vm。$set以确保新添加的属性也是响应的。

让我们看看vue的set方法是如何实现的。set方法是在core/observer/index.js中定义的,下面是核心代码。

/** *在对象上设置属性。添加新属性,如果属性*不存在,则*会触发更改通知。*/Export函数集(target : array any | object,key : any,val : any): any {//如果目标是数组,key是有效的数组索引,将调用数组的拼接方法。//如上所述,数组的拼接方法会被重写,重写后的方法会手动Observe //所以vue的set方法。对于数组,是直接调用重写拼接方法if(array . isarray(target)Is validarrayindex(key)){ target . length=math . max(target . length,key) target.split (key,1,Val) return val} //对于对象,如果key是对象中的属性,直接修改值可以触发更新if (key in target!(键入对象。原型){ Target[Key]=Val Return Val }//VUE的响应对象添加了__ob__属性,所以是否是响应对象Const Ob=(Target : Any)。_ _ Ob _ _//如果它没有响应,Ob){ target[key]=val return val }//Call definer active将getter和setter添加到数据中。//所以vue的set方法调用defineReactive来重新定义响应对象。defineresponsive函数defineresponsive (ob。value,key,val) ob.dep.notify () returnval}在set方法中,目标是一个数组,对象被单独处理。如果目标是一个数组,重写的拼接方法将被调用来手动观察。

对于对象,如果键是对象的属性,直接修改值触发更新;否则,调用defineReactive方法重新定义响应对象。

如果通过proxy实现,Proxy通过set(目标、prop键、值、接收者)拦截对象属性的设置,可以拦截对象的新属性。

不仅如此,Proxy的数组方法也是可以监控的,不需要像上面vue2.x的源代码那样去黑。

完美!

3.代理支持13个拦截操作,这是defineProperty所没有的

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(目标):截取对象的循环.在中,并返回一个数组。方法返回目标对象本身所有属性的属性名,而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)并返回一个布尔值。如果目标对象是一个函数,还有两个额外的操作可以被拦截。

应用(目标、对象、参数):截取代理实例作为函数调用,例如代理(.args),proxy.call(object,args),proxy.apply(.).

Construct(target,args):截取Proxy实例作为构造函数调用的操作,如new proxy(.args)。

4.新标准绩效奖金

代理是一个新的标准。从长远来看,JS引擎会继续优化Proxy,但是getter和setter不会有针对性地优化。

5.代理兼容性差

可见,Proxy对于IE浏览器来说是一场灾难。

目前还没有完全支持Proxy所有拦截方式的Polyfill方案,有一个由google编写的proxy-polyfill,只支持get、set、apply和构造拦截,可以支持IE9和Safari 6。

四.摘要

Object.defineProperty在数组和对象上的表达式并不是不能监控数组下标的变化。vue2.x中通过数组索引自动更新响应数据的失败,是vue本身的设计造成的,而不是defineProperty的锅。Object.defineProperty和Proxy的本质区别是defineProperty只能劫持属性,所以需要递归遍历,新属性需要人工观察。Proxy作为一种新标准,必然会被浏览器厂商不断优化,但其兼容性也很难,目前也没有完整的polifill方案。参考

https://developer . Mozilla . org/zh-CN/docs/Web/JavaScript/Reference/Global _ Objects/Proxy

https://www.jb51.net/article/171872.htm

https://zhuanlan.zhihu.com/p/35080324

http://es6.ruanyifeng.com/#docs/proxy

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

版权声明:为什么Vue3.0使用代理来监控数据(定义属性意味着不支持这个锅)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。