前言
在Vue2的官方文档中,不知道各位小伙伴注意到文档中有明确的表示:
检测变化的注意事项
关于对象的问题
- 新增未声明的属性
- 删除属性
var vm = new Vue({
data:{
message: 'hello'
}
})
vm.message = 'hello vue' // `vm.message` 是响应式的
vm.describe = 'hello Vue.js' // `vm.describe` 是非响应式的
// 非响应式的
delete vm.message
源码调试如下:
通过调试我们可以看到,
不存在data
对象的属性、删除属性,都无法转化成响应式
Vue为什么不能检测对象的变动
- Vue 无法检测实例被创建时不存在于
data
中的 property
由于 Vue 会在初始化实例时对 property 执行
getter/setter
转化,所以 property 必须在data
对象上存在才能让 Vue 将它转换为响应式的。
- Vue 不允许动态添加响应式 property,所以你必须在初始化实例前声明所有响应式 property
getter/setter只能追踪一个数据是否被更改,无法追踪新增属性和删除属性
关于数组的问题
- 当你利用索引直接设置一个数组项时
- 当你修改数组的长度时
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的
视图没有发生变化
Vue为什么不能检测数组的变动
首先,先说结论:为什么vue没有提供对数组属性的监听, 原因是:性能问题。
即:性能代价和获得的用户体验收益不成正比。
变化侦测
什么是变化侦测?
变化侦测就是追踪状态,亦或者说是数据的变化,一旦发生了变化,就要去更新视图。
变化侦测代码
源码调试
注: 本次调试使用的是v2.7.14版本。
Observer源码
我们发现,当侦测的数据是数组
时,如果是数组,则判断是否支持__proto__
,如果支持则使用protoAugment去覆盖原型
很明显,源码中对数组做了单独的处理
.
为什么会停止对数据的检测呢?
我们修改一段源码去一探究竟
修改源码
if(Array.isArray(value)){
value.forEach((item, index) => {
defineReactive(value, index, item, undefined, shallow, mock);
});
// 移除以前的代码
...
}
验证
我们再次调试后,让我们见证奇迹吧
点击按钮后,视图也会发生了变化
:
如此简单的事情,尤大为何多此一举呢?
深入探究
我们用循环将数组数据塞入1000个元素,数据更新为:[0, 1, 2..., 998, 999]。
然后在set中加入时间也是ms级。
更可况,console.log输出也是及占时间的
肯定在其他的问题上
我们再次将数组的每一项换成一个Objcet,让其更接近项目
let temps = []
for (let i = 0; i < 1000; i++) {
temps.push({
index: i,
value: i + '',
arr: [i]
})
}
this.items = temps
this.items[0] = {
index: 0,
value: 999 + '',
arr: [999]
}
再次运行后,页面更新也是ms级,没有一丝的卡顿
此时,设置数组的某一项值的研究暂时没有眉目了。
寻找修改数组的长度的突破
于是,我们将数组的长度修改为10
setLen() {
this.items.length = 10
}
视图并没有进行更新
, 原因也是isArray()
的代码中需要知道newArray
此时,我们要是知道新的数据了,我们都可以直接去更新了
Array的问题
没有更新的原因,就是vue是在拦截器中获取vue实例。
而length只是一个属性
正是这种处理方式,上述的操作是拦截不到的
源码如下:
图解如下:
我们将源码还原后,再次改造
发现也是无法响应的
Observer中的isArray已还原
查阅
源码研究至此,暂时没有什么新的突破,于是查阅了一下资料,果然有新发现
有兴趣的掘友们,可以继续研究,我们一起探讨
最后期待您的一键三连
Proxy
Vue3是基于 JavaScript Proxy(代理) 实现响应式的。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
但Proxy 对 IE 不友好
,vue3 在检测到使用 IE 的情况下(包括 IE11),会自动降级为 Object.defineProperty的数据监听系统
API
当然Vue中也给出了详细的解决方案:
- Vue.set( target, propertyName/index, valu…
- Vue.delete( target, propertyName/index )
- Array.prototype.splice()
总结
- 由于JavaScript原生不够强大,导致了框架设计的局限性。(ES5的Object.defineProperty),导致Vue无法追踪到数据的变化
- 性能与用户体验,有时需要进行取舍