记一次思否问答的问题思考:Vue为何不能检测数组变更

问题来源:https://segmentfault.com/q/10...vue

问题描述:Vue检测数据的变更是经过Object.defineProperty实现的,因此没法监听数组的添加操做是能够理解的,由于是在构造函数中就已经为全部属性作了这个检测绑定操做。git

可是官方的原文:因为 JavaScript 的限制, Vue 不能检测如下变更的数组:github

当你利用索引直接设置一个项时,例如: vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如: vm.items.length = newLength

这句话是什么意思?我测试了下Object.defineProperty是能够经过索引属性来设置属性的访问器属性的,那为什么作不了监听?segmentfault

有些论坛上的人说由于数组长度是可变的,即便长度为5,可是未必有索引4,我就想问问这个答案哪里来的,修改length,新增的元素会被添加到最后,它的值为undefined,经过索引同样能够获取他们的值,怎么就叫作“未必有索引4”了呢?数组

既然知道数组的长度为什么不能遍历全部元素并经过索引这个属性所有添加set和get不就能够同时更新视图了吗?函数

若是非要说的话,考虑到性能的问题,假设元素内容只有4个有意义的值,可是长度确实1000,咱们不可能为1000个元素作检测操做。可是官方说的因为JS限制,我想知道这个限制是什么内容?各位大大帮我解决下这个问题,感谢万分性能



面对这个问题,我想说的是,首先,长度为1000,但只有4个元素的数组并不必定会影响性能,由于js中对数据的遍历除了for循环还有forEach、map、filter、some等,除了for循环外(for,for...of),其余的遍历都是对键值的遍历,也就是除了那四个元素外的空位并不会进行遍历(执行回调),因此也就不会形成性能损耗,由于循环体中没有操做的话,所带来的性能影响能够忽略不计,下面是长度为10000,但只有两个元素的数组分别使用for及forEach遍历的结果:测试

var arr = [1]; arr[10000] = 1
function a(){
    console.time()
    for(var i = 0;i<arr.length;i++)console.log(1)
    console.timeEnd()
}
a(); //default: 567.1669921875ms
a(); //default: 566.2451171875ms

function b(){
    console.time()
    arr.forEach(item=>{console.log(2)})
    console.timeEnd()
}
b(); //default: 0.81982421875ms
b(); //default: 0.434814453125ms

能够看到结果很是明显,不过,若是for循环中不作操做的话二者速度差很少spa

其次,我要说的是,我也不知道这个限制是什么      (⇀‸↼‶)      ╮( •́ω•̀ )╭3d

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。数组的索引也是属性,因此咱们是能够监听到数组元素的变化的

var arr = [1,2,3,4]
arr.forEach((item,index)=>{
    Object.defineProperty(arr,index,{
        set:function(val){
            console.log('set')
            item = val
        },
        get:function(val){
            console.log('get')
            return item
        }
    })
})
arr[1]; // get  2
arr[1] = 1; // set  1

可是咱们新增一个元素,就不会触发监听事件,由于这个新属性咱们并无监听,删除一个属性也是。

再回到题主的问题,既然数组是能够被监听的,那为何vue不能检测vm.items[indexOfItem] = newValue致使的数组元素改变呢,哪怕这个下标所对应的元素是存在的,且被监听了的?

为了搞清楚这个问题,我用vue的源码测试了下,下面是vue对数据监测的源码:
Observer

能够看到,当数据是数组时,会中止对数据属性的监测,咱们修改一下源码:
修改Observer

使数据为数组时,依然监测其属性,而后在defineReactive函数中的get,set打印一些东西,方便咱们知道调用了get以及set。这里加了个简单判断,只看数组元素的get,set
修改defineReactive

而后写了一个简单案例,主要测试使用vm.items[indexOfItem] = newValue改变数组元素能不能被监测到,并响应式的渲染页面
简单案例

运行页面
数组测试

能够看到,运行了6次get,咱们数组长度为3,也就是说数组被遍历了两遍。两遍很少,页面渲染一次,可能屡次触发一个数据的监听事件,哪怕这个数据只用了一次,具体的须要看尤大代码怎么写的。就拿这个来讲,当监听的数据为数组时,会运行dependArray函数(代码在上面图中get的实现里),这个函数里对数组进行了遍历取值操做,因此会多3遍get,这里主要是vue对data中arr数组的监听触发了dependArray函数。

当咱们点击其中一个元素的时候,好比我点击的是3
点击3

能够看到会先运行一次set,而后数据更新,从新渲染页面,数组又是被遍历了两遍。

可是!!!数组确实变成响应式的了,也就是说js语法功能并不会限制数组的监测。

这里咱们是用长度为3的数组测试的,当我把数组长度增长到9时
新数组测试

能够看到,运行了18次get,数组仍是被遍历了两遍,点击某个元素同理,渲染的时候也是被遍历两次。
新数组测试

有了上面的实验,个人结论是数组在vue中是能够实现响应式更新的,可是不明白尤大是出于什么考虑,没有加入这一功能,但愿有知道的大佬们不吝赐教


2018-07-27补充

github上提问了尤大
github提问

相关文章
相关标签/搜索