vue3.0的pre-alpha版代码已经开源了,就像作者之前放出的消息一样,其数据响应这一部分已经由ES6的Proxy
来代替Object.defineProperty
实现,感兴趣的同学可以看其实现源码。vue都开始使用Proxy来实现数据的响应式了,所以有必要抽点时间了解下Proxy。
Object.defineProperty的缺陷
说到Proxy,就不得不提Object.defineProperty
,我们都知道,vue3.0之前的版本都是使用该方法来实现数据的响应式,具体是:
通过设定对象属性getter/setter方法来监听数据的变化,同时getter也用于依赖收集,而setter在数据变更时通知订阅者更新视图。
大概如下代码所示:
function defineReactive(obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
collectDeps() // 收集依赖
return value
},
set(newVal) {
observe(newVal); // 若是对象需要递归子属性
if (newVal !== value) {
notifyRender() // 通知订阅者更新
value = newVal;
}
}
})
}
function observe(obj) {
if (!obj || typeof obj! === 'object') {
return
}
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key]);
})
}
var data = {
name: 'wonyun',
sex: 'male'
}
observe(data)
虽然Object.defineProperty通过为属性设置getter/setter能够完成数据的响应式,但是它并不算是实现数据的响应式的完美方案,某些情况下需要对其进行修补或者hack,这也是它的缺陷,主要表现在两个方面:
无法检测到对象属性的新增或删除
由于js的动态性,可以为对象追加新的属性或者删除其中某个属性,这点对经过Object.defineProperty方法建立的响应式对象来说,只能追踪对象已有数据是否被修改,无法追踪新增属性和删除属性,这就需要另外处理。
目前Vue保证响应式对象新增属性也是响应式的,有两种方式:
Vue.set(obj, propertName/index, value)
响应式对象的子对象新增属性,可以给子响应式对象重新赋值
data.location = { x: 100, y: 100 } data.location = {...data, z: 100}
响应式对象删除属性,可以使用
Vue.delete(obj, propertyName/index)
或者vue.$delete(obj, propertyName/index)
; 类似于删除响应式对象子对象的某个属性,也可以重新给子对象赋值来解决。不能监听数组的变化
vue在实现数组的响应式时,它使用了一些hack,把无法监听数组的情况通过重写数组的部分方法来实现响应式,这也只限制在数组的
push/pop/shift/unshift/splice/sort/reverse
七个方法,其他数组方法及数组的使用则无法检测到,例如如下两种使用方式:vm.items[index] = newValue
vm.items.length--
那么vue怎么实现数组的响应式呢,并不是重写数组的
Array.prototype
对应的方法,具体来说就是重新指定要操作数组的prototype,并重新该prototype中对应上面的7个数组方法,通过下面代码简单了解下实现原理:const methods = ['pop','shift','unshift','sort','reverse','splice', 'push']; // 复制Array.prototype