vue监听数组元素属性的变化_为什么Vue3.0不再使⽤
defineProperty实现数据监听?
作者:我叫于是乎
导读
vue3.0中,响应式数据部分弃⽤了Object.defineProperty,使⽤Proxy来代替它。本⽂将主要通过以下⽅⾯来分析为什么vue选择弃⽤
Object.defineProperty。
1. Object.defineProperty真的⽆法检测数组下标的变化吗?
2. 分析vue2.x中对数组Observe部分源码
3. 对⽐Object.defineProperty和Proxy
⼀⽆法监控到数组下标的变化?
在⼀些技术博客上看到过这样⼀种说法,认为 Object.defineProperty 有⼀个缺陷是⽆法监听数组变化:
⽆法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应。所以vue才设置了7个变异数组(push、pop、
shift、unshift、splice、sort、reverse)的 hack ⽅法来解决问题。
Object.defineProperty的第⼀个缺陷,⽆法监听数组变化。 然⽽Vue的⽂档提到了Vue是可以检测到数组变化的,但是只有以下⼋种
⽅法,vm.items[indexOfItem] = newValue这种是⽆法检测的。
这种说法是有问题的,事实上,Object.defineProperty 本⾝是可以监控到数组下标的变化的,只是在 Vue 的实现中,从性能/体验的性价
⽐考虑,放弃了这个特性。
下⾯我们通过⼀个例⼦来为 Object.defineProperty 正名:
function defineReactive(data, key, value) {  Object.defineProperty(data, key, {    enumerable: true,    configurable: true,    get: function defineGet() {      console.lo
上⾯代码对数组arr的每个属性通过 Object.defineProperty 进⾏劫持,下⾯我们对数组arr进⾏操作,看看哪些⾏为会触发数组的 getter
和 setter ⽅法。
1. 通过下标获取某个元素和修改某个元素的值
可以看到,通过下标获取某个元素会触发 getter ⽅法, 设置某个值会触发 setter⽅法。
接下来,我们再试⼀下数组的⼀些操作⽅法,看看是否会触发。
2. 数组的 push ⽅法
push 并未触发 setter 和 getter ⽅法,数组的下标可以看做是对象中的 key ,这⾥push 之后相当于增加了下索引为3的元素,但是并未对新的下标进⾏ observe ,所以不会触发。
3.数组的 unshift ⽅法
刚刚发⽣了什么?
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的元素发⽣变化才会触发,其他的新增索引,就相当于对象中新增的属性,需要再⼿动 observe 才可以。
4. 数组的 pop ⽅法
当移除的元素为引⽤为2的元素时,会触发 getter 。
删除了索引为2的元素后,再去修改或获取它的值时,不会再触发 setter 和getter 。
electron vue教程
这和对象的处理是同样的,数组的索引被删除后,就相当于对象的属性被删除⼀样,不会再去触发 observe。
到这⾥,我们可以简单的总结⼀下结论。
Object.defineProperty 在数组中的表现和在对象中的表现是⼀致的,数组的索引就可以看做是对象中的 key。
1. 通过索引访问或设置对应元素的值时,可以触发 getter 和 setter ⽅法
2. 通过 push 或 unshift 会增加索引,对于新增加的属性,需要再⼿动初始化才能被observe。
3. 通过 pop 或 shift 删除元素,会删除并更新索引,也会触发 setter 和 getter⽅法。
所以,Object.defineProperty 是有监控数组下标变化的能⼒的,只是vue2.x放弃了这个特性。
⼆ vue对数组的observe做了哪些处理?
vue的 Observer 类定义在 core/observer/index.js 中
可以看到,vue的 Observer 对数组做了单独的处理。
hasProto 是判断数组的实例是否有 __proto__ 属性,如果有 __proto__ 属性就会执⾏ protoAugment ⽅法,将 arrayMethods 重写到原型上。 hasProto 定义如下。
arrayMethods 是对数组的⽅法进⾏重写,定义在 core/observer/array.js 中, 下⾯是这部分源码的分析。
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */import { def } from '../util/index'// 复制数组构三 Object.defineProperty VS Proxy
上⾯已经知道 Object.defineProperty 对数组和对象的表现是⼀致的,那么它和Proxy 对⽐存在哪些优缺点呢?
1. Object.defineProperty只能劫持对象的属性,⽽Proxy是直接代理对象。
由于 Object.defineProperty 只能对属性进⾏劫持,需要遍历对象的每个属性,如果属性值也是对象,则需要深度遍历。⽽ Proxy 直接代
理对象,不需要遍历操作。
2. Object.defineProperty对新增属性需要⼿动进⾏Observe。
由于 Object.defineProperty 劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使⽤ Object.defineProperty
进⾏劫持。
也正是因为这个原因,使⽤vue给 data 中的数组或对象新增属性时,需要使⽤vm.$set 才能保证新增的属性也是相应式的。
下⾯看⼀下vue的 set ⽅法是如何实现的,set⽅法定义在core/observer/index.js ,下⾯是核⼼代码。
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. */export function set (targe
在 set ⽅法中,对 target 是数组和对象做了分别的处理,target 是数组时,会调⽤重写过的 splice ⽅法进⾏⼿动 Observe 。
对于对象,如果 key 本来就是对象的属性,则直接修改值触发更新,否则调⽤defineReactive ⽅法重新定义响应式对象。
如果采⽤ proxy 实现,Proxy 通过 set(target, propKey, value, receiver) 拦截对象属性的设置,是可以拦截到对象的新增属性的。
不⽌如此,Proxy 对数组的⽅法也可以检测到,不需要像上⾯vue2.x源码中那样进⾏ hack。
完美
3. Proxy⽀持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 in proxy的操作,返回⼀个布尔值。
deleteProperty(target, propKey):拦截delete proxy[propKey]的操作,返回⼀个布尔值。
ownKeys(target):拦截OwnPropertyNames(proxy)、OwnPropertySymbols(proxy)、
Object.keys(proxy)、in循环,返回⼀个数组。该⽅法返回⽬标对象所有⾃⾝的属性的属性名,⽽Object.keys()的返回结果仅包括⽬标对象⾃⾝的可遍历属性。
getOwnPropertyDescriptor(target, propKey):拦截OwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)、
Object.defineProperties(proxy, propDescs),返回⼀个布尔值。
preventExtensions(target):拦截Object.preventExtensions(proxy),返回⼀个布尔值。
getPrototypeOf(target):拦截PrototypeOf(proxy),返回⼀个对象。
isExtensible(target):拦截Object.isExtensible(proxy),返回⼀个布尔值。
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)。
4. 新标准性能红利
Proxy 作为新标准,长远来看,JS引擎会继续优化 Proxy,但 getter 和 setter基本不会再有针对性优化。
5. Proxy兼容性差
可以看到,Proxy对于IE浏览器来说简直是灾难。
四总结
1. Object.defineProperty 对数组和对象的表现⼀致,并⾮不能监控数组下标的变化,vue
2.x中⽆法通过数组索引来实现响应式数据的⾃动更新是vue本⾝的设计导致的,不是 defineProperty 的锅。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。