响应式侦测
1. 对象侦测
Vue2 实现方式
Vue2 使用 Object.defineProperty 来检测对象的变化。
javascript
let car = {}
let val = 3000
Object.defineProperty(car, 'price', {
enumerable: true,
configurable: true,
get() {
console.log('price属性被读取了')
return val
},
set(newVal) {
console.log('price属性被修改了')
val = newVal
}
})
Vue2 通过递归的 Observer 将对象中的所有属性转换为可观测对象
在 getter 中收集依赖,在 setter 中通知依赖更新
依赖:数据变化的时候会去通知视图,但是不能全局去通知,所以要收集谁使用了这个数据,这个谁,就是依赖
在vue中,谁得到数据谁就是依赖,会给谁创建一个Water实例,用这个实例来代表依赖,由Water去通知真正的依赖,上方那个update就是Water的东西
vue在getter里面收集依赖,在setter里面通知依赖去进行更新
vue为每个数据都建立了一个依赖管理器,把这个数据的所有依赖都管理起来
依赖管理器实现:
javascript
// 依赖管理器
export default class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
removeSub(sub) {
const index = this.subs.indexOf(sub)
if (index > -1) {
this.subs.splice(index, 1)
}
}
depend() {
if (window.target) {
this.addSub(window.target)
}
}
notify() {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 通知依赖更新
}
}
}
流程:
数据通过监听器转换成拥有 getter 和 setter 的形式
外界通过 Watcher 读取数据,触发 getter 将 Watcher 添加到依赖数组
数据变化时,setter 调用 Watcher(依赖)发送通知
Watcher 最终通知外界,引发视图更新或其他操作
Vue3 实现方式
Vue3 使用 Proxy 来检测变化。
- 可以直接监听整个对象,无需递归遍历
- 可以监听新增和删除的属性
- 性能更好
2. 数组侦测
Vue2 实现方式
数组无法直接使用 Object.defineProperty 进行侦测。
- 封装数组的原生方法,使其能够被侦测
- 使用变异方法(mutator methods)来触发更新
Array原型中可以改变数组自身内容的方法有7个,分别是push,pop,shift,unshift,splice,sort,reverse
实现原理:
- 创建继承自
Array.prototype的新对象 - 重写上述7个方法,在调用原生方法后手动触发更新通知
- 数组依然使用 getter 收集依赖,但通过封装的数组方法感知变化
不足的地方在于,通过下标操作的时候,vue无法监听到,所以vue提供了Vue.set和Vue.delete这几个方法
Vue3 实现方式
Vue3 使用 Proxy 可以监听到数组的所有变化。
- 可以直接监听数组下标操作
- 可以监听数组长度的变化
- 无需特殊处理数组方法
示例:
javascript
const array = [1, 2, 3]
const proxy = new Proxy(array, {
set(target, property, value, receiver) {
console.log(`数组的${property}属性被修改为${value}`)
return Reflect.set(target, property, value, receiver)
}
})
总结对比
| 特性 | Vue2 (Object.defineProperty) | Vue3 (Proxy) |
|---|---|---|
| 对象监听 | 需要递归遍历对象属性 | 直接监听整个对象 |
| 新增属性 | 无法自动侦测,需要Vue.set | 自动侦测 |
| 删除属性 | 无法自动侦测,需要Vue.delete | 自动侦测 |
| 数组监听 | 重写7个数组方法 | 直接监听所有变化 |
| 数组下标操作 | 无法侦测 | 可以侦测 |
| 性能 | 递归初始化成本高 | 惰性监听,性能更好 |