为什么vue3响应式数据用proxy?
vue3之前vue使用Object.defineProperty进行数据劫持,实现vue的双向绑定。为什么vue3使用Proxy? Object.defineProperty有什么问题呢?
Object.defineProperty
# Object.defineProperty() | MDN
- 作用:在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
- 语法:Object.defineProperty(obj, prop, descriptor)
监听对象的一个属性
let name1 = 'rain';
const somebody = {name: 'rain'};
Object.defineProperty(somebody, 'name', {
get: function() {
console.log('获取');
return name1;
},
set: function(newVal) {
console.log('设置', newVal);
name1 = newVal;
}
})
console.log(somebody.name); // rain
somebody.name = 'sugar'
console.log(somebody.name); // sugar
监听对象上全部属性
定义defineProperty函数,Observe获取对象全部属性,进行监听。
const defineProperty = (obj, key, val) => {
Object.defineProperty(obj, key, {
get: function () {
return val
},
set: function (newVal) {
val = newVal
}
})
}
const Observe=(obj) => {
Object.keys(obj).forEach(key => {
defineProperty(obj, key, obj[key])
})
}
const somebody = {name: 'rain', age: 18, sex: 'female'}
Observe(somebody)
// 触发get
console.log('somebody.name ====', somebody.name);
// 触发set
somebody.name = 'sugar'
监听对象上属性为对象时,递归
const defineProperty = (obj, key, val) => {
// 重点1
if (typeof val === 'object' && val !== null) {
Observe(val)
}
Object.defineProperty(obj, key, {
get: function () {
return val
},
set: function (newVal) {
// 重点2: 新属性为对象时也加上监听
if (typeof newVal === 'object' && newVal !== null) {
Observe(newVal)
}
val = newVal
}
})
}
const Observe = (obj) => {
Object.keys(obj).forEach(key => {
defineProperty(obj, key, obj[key])
})
}
const somebody = {name: 'rain', age: 18, sex: 'female', family: {mother: 'mama', father: 'baba'}}
Observe(somebody)
// 触发get
console.log('somebody.family.mother ====', somebody.family.mother);
// 触发set
somebody.family.father = {name: 'papa', age: 48}
console.log('somebody.family.father ====', somebody.family.father.name);
对象属性值为数组呢?
const defineProperty = (obj, key, val) => {
if (typeof val === 'object' && val !== null) {
Observe(val)
}
Object.defineProperty(obj, key, {
get: function () {
console.log('获取key为:', key)
return val
},
set: function (newVal) {
console.log(`${key}属性被修改为${newVal}了`)
val = newVal
}
})
}
const Observe = (obj) => {
Object.keys(obj).forEach(key => {
defineProperty(obj, key, obj[key])
})
}
const somebody = {name: 'rain', age: 18, sex: 'female', family: [
'mama', 'baba'
]}
Observe(somebody)
// 触发get
console.log('somebody.family ====', somebody.family);
// 触发set
somebody.family = ['mama', 'baba', 'didi']
// 重点:触发不了set方法
somebody.family.pop()
somebody.family.push('sister')
可得:Object.defineProperty不能够监听到数组length的变化
总结
- 一次只能对一个属性监听,监听多个属性需要遍历
- 数据层级比较深时,对要用递归,计算量大
- 新增属性需要手动添加监听,不然没办法监听到
- 不能够监听到数组length的变化,vue2是做了额外处理
Proxy
所以proxy出现来解决上述问题, proxy | MDN
Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
语法:const p = new Proxy(target, handler)
const somebody = {name: 'rain', age: 18, sex: 'female', family: {people: []}}
const person = [
'小花', '小美', '小好'
]
const hanlder = {
get: function(obj, prop) {
return prop in obj ? obj[prop] : '默认值';
},
set: function(obj, prop, value) {
obj[prop] = value;
// 表示成功
return true;
}
}
const somebodyProxy = new Proxy(somebody, hanlder)
// 触发get
console.log('somebody.name1 ====', somebodyProxy.name1); // 默认值
// 触发set
somebodyProxy.family.people = ['mom', 'dad']
// 数组
const personProxy = new Proxy(person, handler)
// 触发set
personProxy.pop()
personProxy.push('弟弟')
personProxy.push('妹妹')
console.log('somebody ====', somebodyProxy);
/** 打印结果,证明上述操作都可以监听到:
* somebody.name1 ==== 默认值
* somebody ==== {
* name: 'rain',
* age: 18,
* sex: 'female',
* family: { people: ['mom', 'dad'] }
* }
*/
总结:
- 不用遍历
- 不用递归
- 新添加属性可以监听
- 数组也可以监听 上述问题都得以解决,当然要用Proxy啦