携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
前言
都知道,vue2的数据响应式用的是 Object.defineProperty 做的数据劫持,而在vue3中用的proxy做的数据劫持, 这就能体现出两者的不同的,既然放弃了Object.defineProperty选择proxy,那么两者之间有什么不同点呢?
- defineProperty劫持的是对象的属性,proxy代理的是整个对象
- defineProperty劫持的数据类型有限,proxy更加丰富
- defineProperty返回的是被修改的对象,proxy对象用于创建一个对象的代理
- defineProperty无法监听新增属性和删除属性
Object.defineProperty
认识
它是可以修改或者添加对象的属性,并返回改对象
Object.defineProperty(obj,prop,descriptor)
obj:要定义属性的对象
prop:要定义或者修改的属性的名称或者symbol
descriptor:要定义或者修改的属性的配置:
value:该属性对应的有效的JavaScript值;
const person = {};
Object.defineProperty(person, 'age', {
value: 42,
});
console.log(person.age);//42
writable:为true时属性值才能被修改,默认为false
'use strict'
const person = {};
Object.defineProperty(person, 'age', {
value: 42,
writable: false
});
person.age = 77;
// 在严格模式下:Cannot assign to read only property 'age' of object '#<Object>'
console.log(person.age);
get:访问当前属性时,会执行该函数
'use strict'
const person = {};
var age = 42
Object.defineProperty(person, 'age', {
get() {
console.log("get函数被执行")//get函数被执行
return age
}
});
console.log(person.age);//42
set:当属性值被修改时,会调用此函数,该方法接受一个参数(新值),会传入赋值时的this对象
'use strict'
const person = {};
var age = 42
Object.defineProperty(person, 'age', {
get() {
console.log("get函数被执行")
return age
},
set(val) {
console.log(`未修改之前的age属性值=${this.age}`)
console.log("set函数被执行")
age = val
}
});
person.age = 77;
console.log(person.age);
打印的执行顺序:
get函数被执行
未修改之前的age属性值=42
set函数被执行
get函数被执行
77
enumerable:该属性是否能出现在对象的枚举属性中
'use strict'
const person = {};
person.name = '张三'
var age = 42
Object.defineProperty(person, 'age', {
// value: 42,
// writable: true,
get() {
console.log("get函数被执行")
return age
},
set(val) {
console.log(`未修改之前的age属性值=${this.age}`)
console.log("set函数被执行")
age = val
}
});
Object.keys(person).forEach(it=>{
console.log(it)
})
打印结果:name
由此可见:enumrable默认为false,当直接使用赋值的方式创建对象的属性时,为true
修改后:
'use strict'
const person = {};
person.name = '张三'
var age = 42
Object.defineProperty(person, 'age', {
// value: 42,
// writable: true,
enumrable:true
get() {
console.log("get函数被执行")
return age
},
set(val) {
console.log(`未修改之前的age属性值=${this.age}`)
console.log("set函数被执行")
age = val
}
});
Object.keys(person).forEach(it=>{
console.log(it)
})
打印结果:name age
- ``configurable
:判断当前属性是否可以被删除及除value和writable`之外其他特性是不是能被修改
1.
'use strict'
const person = {};
person.name = '张三'
var age = 42
Object.defineProperty(person, 'age', {
enumerable:true,
get() {
console.log("get函数被执行")
return age
},
set(val) {
console.log(`未修改之前的age属性值=${this.age}`)
console.log("set函数被执行")
age = val
}
});
delete person.age // Cannot delete property 'age' of #<Object>
加入configurable:true之后
console.log(person.age) // undefined
2.
'use strict'
const person = {};
person.name = '张三'
var age = 42
Object.defineProperty(person, 'age', {
enumerable:true,
// configurable:true,
get() {
console.log("get函数被执行")
return age
},
set(val) {
console.log(`未修改之前的age属性值=${this.age}`)
console.log("set函数被执行")
age = val
}
});
Object.defineProperty(person,'age',{
get(){
return 1
}
})
结果:Cannot redefine property: age
configurable:true时
console.log(person.age) //1
- 注:属性不能同时拥有
value或writable和get和set
监听对个对象属性
上面的例子都是使用一个属性,这里我们监听多个属性,使用Object.keys()或者for in 结合来做:
'use strict'
const person = {
name: "张三",
age: "20"
};
const observe = (obj, key, value) => {
Object.defineProperty(person, key, {
enumerable: true,
configurable: true,
get() {
console.log("get函数被执行")//第1个执行,最后打印person.name时第5个执行
return value
},
set(val) {
console.log(`未修改之前的age属性值=${value}`) //第3个执行
console.log("set函数被执行") //第四个执行
value = val
}
});
}
Object.keys(person).forEach(key => {
observe(person, key, person[key])
})
console.log(person.age)//第2个执行
person.name = '李四'
console.log(person.name)//第6个执行
结果执行顺序:
get函数被执行
20
未修改之前的age属性值=张三
set函数被执行
get函数被执行
李四
递归监听深层对象
这里我们使用递归来做一个深层的监听,基于上述代码可以看到,observe是一个只对属性进行监听的函数,object.keys返回一个包含对象的属性的数组,循环进行把参数传入observe函数,那我们可以在observe函数里面判断value值是不是object类型的,是的话,再使用object.keys循环不就好了吗?,~,把提取属性和循环包装成一个函数吧:
'use strict'
const person = {
name: "张三",
age: "20",
children: {
name: "张三-1",
age: "20-1"
}
};
const observe = (obj, key, value) => {
if (typeof value === 'object') {
observer(value)
}
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log("get函数被执行")
return value
},
set(val) {
console.log(`未修改之前的age属性值=${value}`)
console.log("set函数被执行")
value = val
}
});
}
const observer = (obj) => {
if (typeof obj !== 'object' || obj === null) return
Object.keys(obj).forEach(key => {
observe(obj, key, obj[key])
})
}
observer(person)
console.log(person.age) // 20
person.name = '李四'
console.log(person.name) // 李四
console.log("打印children里面的属性") //打印children里面的属性
console.log(person.children.name) //张三-1
person.children.age = 300;
console.log(person.children.age) //300
浏览器打印的结果顺序为:
get函数被执行
20
未修改之前的age属性值=张三
set函数被执行
get函数被执行
李四
打印children里面的属性
get函数被执行
get函数被执行
张三-1
get函数被执行
未修改之前的age属性值=20-1
set函数被执行
get函数被执行
get函数被执行
300
监听数组
现在我们用object.defineProperty来监听数组的变化,使用push、pop、shift、unshift这个方法及以数组下标为key(相当于对象的属性名)探讨是否可以监听到数组的变化:
const arr = [1, 2, 3, 4];
const observeArr = (arr, key, val) => {
Object.defineProperty(arr, key, {
get() {
console.log("执行get函数", `当前的key=${key}`, `当前的val值${val}`)
return val
},
set(newval) {
console.log("执行set函数", `当前的key=${key}`, `当前的val值${val}`, `新的值${newval}`)
val = newval
}
})
}
arr.forEach((it, index) => {
observeArr(arr, index, it)
})
arr.pop() //执行get函数 当前的key=3 当前的val值4
// arr.shift()
// 执行get函数 当前的key=0 当前的val值1
// 执行get函数 当前的key=1 当前的val值2
// 执行set函数 当前的key=0 当前的val值1 新的值2
// 执行get函数 当前的key=2 当前的val值3
// 执行set函数 当前的key=1 当前的val值2 新的值3
// 执行get函数 当前的key=3 当前的val值4
// 执行set函数 当前的key=2 当前的val值3 新的值4
// arr.unshift(9)
// 执行get函数 当前的key=3 当前的val值4
// 执行get函数 当前的key=2 当前的val值3
// 执行set函数 当前的key=3 当前的val值4 新的值3
// 执行get函数 当前的key=1 当前的val值2
// 执行set函数 当前的key=2 当前的val值3 新的值2
// 执行get函数 当前的key=0 当前的val值1
// 执行set函数 当前的key=1 当前的val值2 新的值1
// 执行set函数 当前的key=0 当前的val值1 新的值9
//arr.push(2)// ''
push:当push值的时候并没有执行set函数和get函数,并不能监听到数组的push方法的操作;shift:能够触发get和set;unshift:能够触发get和setpush:无法触发,新增的索引并没有监听