今天继续学习vue3-比较vue2和vue3的响应式原理。
vue2中的问题
-
对象直接添加新的属性或删除已有属性,界面不会自动更新,不是响应式。
-
直接通过下标修改(arr[1]=xxx)或更新数组的length,界面不会自动更新,不是响应式。
vue3中不会存在上述问题。
vue2响应式原理
核心:
- 对象:通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
// 假设vm是我们的vue实例
const vm = {}
// data数据
const data = {
name: "小红",
age: 18
}
// 遍历data,将data属性绑定在vm上,对属性的读取和修改就行拦截
Object.entries(data).forEach([prop, value]) => {
let initVal = value
Object.defineProperty(vm, prop, {
get () {
console.log('执行get')
return initVal
},
set (newVal) {
console.log('执行set')
initVal = newVal
}
})
}
// 读取属性值
console.log(vm.name) // '执行get' '小红'
// 修改属性值
vm.name = '小明' // '执行set'
console.log(vm.name) // '执行get' '小明'
// 添加属性
vm.sex = '女' // 不会执行set方法
console.log(vm.sex) // 能打印出'女',但是不会执行get方法
- 数组:通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持(数组的push、pop、splice等方法之所以能正常使用,其实是因为vue被重写了)
// 把push,pop等方法放在一个对象里面
const obj = {
push() {},
pop() {},
shift() {},
unshift() {},
splice() {},
sort() {},
reverse() {}
}
// 遍历obj,使用defineProperty监听
Object.keys(obj).forEach(key => {
Object.defineProperty(obj, key, {
value: function(...args) {
return Array.prototype[key].call(this, ...args)
}
})
})
const arr = []
arr._proto_ = obj // 将数组的隐式原型指向obj
// arr._proto_等于它的构造函数的原型,也就是Array.prototype,所以arr可以执行push、pop等方法,但是现在arr._proto_又等于obj了,所以arr.push就相当于obj.push了,而obj.push我们用defineProperty进行了监听,执行obj.push()就会执行value函数
// 测试
arr.push(1) // 执行这一句就相当于执行obj.push(1)
console.log(arr)
vue3响应式原理
核心
- 通过Proxy(代理):拦截对对象本身的操作,包括属性值的读写,属性的添加,属性的删除等...
- 通过Reflect(反射):动态对被代理对象的相应属性进行特定的操作
const user = {
name: "小红",
age: 18
}
// 代理对象
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log("劫持get()", prop)
return Reflect.get(target, prop)
},
set(target, prop, val) {
console.log("劫持set()", prop)
return Reflect.set(target, prop, val)
},
deleteProperty(target, prop) {
console.log("劫持delete", prop)
return Reflect.deleteProperty(target, prop)
}
})
// 读取属性值
console.log(proxyUser === user) // false
console.log(proxyUser.name) // 劫持get() name 小红
// 设置属性值
proxyUser.name = "小明" // 劫持set() name 小明
proxyUser.age = 20
console.log(user)
// 添加属性
proxyUser.sex = "女" // 劫持set() sex 女
console.log(user)
// 删除属性
delete proxyUser.sex // 劫持delete sex
console.log(user)
- 总结:正是由于vue3是使用Proxy代理的方式拦截对象本身,所以在vue3中添加/删除属性都说响应式的,通过下标修改数组也是响应式的。