vue2-Object.defineProperty
vue2的响应式对象的核心是利用了ES5
中 Object.defineProperty
给对象的属性添加了getter
和setter
。
Vue会把props
、data
等变成响应式对象,在创建过程中,发现子属性也为对象时则递归把该对象变成响应式。
触发getter
的时候,触发依赖收集,那么依赖收集做了哪些事情?
getter-依赖收集
vue源码
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() //依赖收集
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() // 派发更新
}
})
访问数据时,触发getter
,调用当前watcher
的addDep()
,收集依赖存在this.subs
(收集当前正在计算的watcher
,然后把这些watcher
作为订阅者),当数据发生改变的时候,会执行setter
,派发更新。
注意点-延伸:在有v-if
和v-else
中视图渲染中,如果当前显示的是v-if
的数据,那么不管v-else
的依赖的数据怎么样变化,都不会触发重现渲染,因为在cleanupDeps
函数中会比较this.newDepIds
和dep
中的watcher,如果没有,则remove
掉,是一个性能上的提升。
总结:依赖收集就是订阅数据变化的watcher的收集,目的是当这些响应式数据发生变化,触发它们的 setter
的时候,能知道应该通知哪一些订阅者去做相应的逻辑处理。
setter-派发更新
触发setter
的时候,派发更新,那么派发更新做了哪些事情?
遍历之前的watcher订阅者,调用订阅者的subs[i].update()
;然后会执行queueWacther()
这个方法,在这里方法里面会将watcher
push 到 queue里面,这个过程是同步执行的,当执行完push操作之后,接着执行flushSchedulerQueue()
这个方法;首先按照从父到子的顺序排序watcher
,然后遍历watcher
,执行watcher.run()
官网解释
链接:https://cn.vuejs.org/v2/guide/reactivity.html
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
对象
Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在
data
对象上存在才能让 Vue 将它转换为响应式的
解决办法
// 添加一个属性
this.$set(this.obj,'b',1) // Vue.set(vm.obj,'b',1)
// 添加多个属性
this.obj = Object.assign({},this.obj,{a:1,b:2})
数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
解决办法
// 解决第一个 Vue.set, Array.prototype.splice
Vue.set(vm.items,indexOfItem,newValue)
vm.items.splice(indexOfItem,1,newValue)
// 解决第二个 Array.prototype.splice
vm.items.splice(newLength)
与Vue.set
对应的是Vue.delete
,Vue.delete( target, propertyName/index
缺点
- 不能监听数组下标及长度变化、实例对象新增或删减属性
- 需要使用递归、闭包,消耗性能和内存
- 代码较复杂,需要做数据备份
vue3–proxy
vue3的响应式对象的核心是利用了ES6
中 proxy
给对象的属性添加了getter
和setter
。
proxy
是在访问对象之前设置一层拦截,对整个对象进行代理(可以是任何类型的对象,包括原生数组、函数,或者是另一个代理)
proxy源码
let obj = new Proxy(ob,{
//target就是第一个参数ob
get: function(target,key,value){
// let result = Reflect.get(target,key)
// return result
return target[key]
},
set: function(target,key,value){
// let result = Reflect.set(target,key,value)
// return result
target[key] = value
}
})
缺点
- 因为
Proxy
是ES6新增的属性,有些浏览器还不支持,只能兼容到IE11
相比Object.defineProperty()
的优点
- 可以监听数组变化或对象属性的增减
- 不需要一次性遍历
data
属性,减少性能消耗 - 代码较简单