我们知道,在Vue中我们操作某个属性时vue可以实时响应,这是因为当一个vue实例被创建出来之后,它会生成一个Observer观察者实例,然后对vue中的对象属性进行遍历,通过defineProperty将它们转换为了getter和setter,当我们操作某个数据时,就会触发getter和setter,从而完成一个监听操作。
下面我们详细解释一下defineProperty以及它的优劣所在:
Object.defineProperty(obj,"",{})
defineProperty总共有三个参数,第一个参数表示我们将在哪个对象上进行操作
第二个参数表示为要操作的是哪个字段,注意,这个参数是一个字符格式的
第三个参数为一个对象,它用来设置我们操作的这个属性需要哪些特性
一、defineProperty的第三个参数总共有六个属性可以设置,第一个属性为 value
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan"
})
obj.name = 666;
console.log(obj.name);
这个属性代表我们设置的这个name字段的值为 "zhangsan" , 注意,现在我们只设置了一个属性【value】,在默认情况下这就是一个只读字段,我们对他进行修改不会有效,因此将他赋值为 666 不会有任何效果。
它的结果仍然是张三
二、 writable
这个属性代表的是这个name字段是否是可写字段,在默认我们不设置的情况下它为false,即不可写,因此上面我们对这个字段进行赋值就无效
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:true,
})
obj.name = 666;
console.log(obj.name);
现在我们将它设置为true,因此此时它是可写属性,所有修改就会有效了
三、enumerable
这个属性用于设置我们定义的这个name字段是否可以被枚举,默认我们不设置的情况下它为false,即不可枚举,因此当我们去查询这个键值时查询不到
(枚举的意思就是是否可以被列举,被查询出来)
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:true,
})
obj.name = 666;
console.log(Object.keys(obj));
//Object.keys方法用于查询此对象中的所有字段,并用一个数组返回
此时因为我们没有设置,因此默认情况下它是查不到这个name字段的
只能查到 age字段,并没有name字段,但若我们将enumerable属性设置为true
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:true,
enumerable:true,
})
obj.name = 666;
console.log(Object.keys(obj));
此时它就可以查到这个name字段了。
四、configurable
这个属性用于设置该字段是否可以被删除,或者是否可以更改它的特性,为false表示不能删除和更改,true则表示允许删除和更改;它的默认值是false,即不允许删除和更改特性。
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:true,
enumerable:true,
configurable:false
})
//delete 关键字用于删除某个对象中的某个属性
delete obj.name;
console.log(obj);
此时我们执行delete是无效的,因为它现在是不可删除的,此时这个name字段仍然存在
并且为false时我们不能再次更改这个字段的特性,否则会报错;
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:true,
enumerable:true,
configurable:false
})
//此处我再次对它的特性进行更改,将writable和enumerable设置为false
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:false,
enumerable:false,
configurable:false
})
但如果configurable为true时,则又是另一种效果:
let obj = {
age:19,
};
Object.defineProperty(obj,"name",{
value:"zhangsan",
writable:true,
enumerable:true,
configurable:true
})
delete obj.name
console.log(obj)
此时我们可以看到,name字段已经被删除了,并且这个时候我们再去更改它的特性它也不会报错了。
五、get和set
这两个属性用于监听字段的读写操作,也是用的最多的两个属性,注意,这两个属性不能和writable以及value属性同时出现,否则会报错。
当我们去操作这个字段时,它就会触发get方法和set方法,从而实现监听操作
let obj = {
age:19,
};
let tempName = ""
Object.defineProperty(obj,"name",{
get(){
console.log("读取了这个属性")
return tempName;
},
set(val){
console.log("设置了这个属性")
tempName = val;
}
})
obj.name = 999;
console.log(obj.name)
使用get和set我么可以做一些有趣的事,比如我们可以让一个变量同时等于多个值
let obj = {
age:19,
};
let tempName = 0;
Object.defineProperty(obj,"name",{
get(){
tempName++;
return tempName;
},
set(val){
tempName = val;
}
})
console.log(obj.name===1 && obj.name===2 && obj.name===3)
缺点
defineProperty无法针对整个对象,它只能给对象的某一个属性进行监听,如果我们想监听整个操作,必须使用遍历方法对整个对象进行遍历,一个一个的对他们执行监听操作,这么做起来比较麻烦。
let data = {
name:"张三",
age:"19",
gender:"男",
}
let temp = {}
Object.keys(data).forEach(key=>{
temp[key] = data[key]
Object.defineProperty(data,key,{
get(){
console.log(`获取了${key}`)
return temp[key]
},
set(val){
console.log(`设置了${key}`)
temp[key] = val;
}
})
})
console.log(data.name)
console.log(data.age)
console.log(data.gender)
其次,第二个问题在于它无法监听一些数组api的操作
push,sort,pop,unshift,shift,splice,reverse
这七个api它监听不到;
let arr = [1,2,3,4,5]
Object.defineProperty(window,"data",{
get(){
console.log("获取了数组")
return arr;
},
set(val){
console.log("设置了数组")
}
})
data.push("12312312")
当我们执行push时,它只触发了get方法,但是set方法却并没有被触发,因为它无法监听push方法的操作;
在Vue中能监听到是因为它自己重写了这些方法,它其实使用了函数劫持的方式,自己对这些方法进行了重写,事实上它对我们在data中定义的数组进行了原型链重写,将其指向了vue自己定义的数组方法上,从而让这些重写的方法来进行数据劫持,这样当我们调用数组api时就可以通知依赖更新。如果数组中还包含了引用类型,它还会对引用类型再次递归遍历进行监控,这样就实现了检测数组的变化;
一个简单实现:
let methods = ["push","pop","splice","sort","unshift","shift","reverse"];
let ArrayM = [];
methods.forEach(method=>{
let original = Array.prototype[method]
ArrayM[method] = function(){
console.log(`调用了${method}`);
return original.apply(this,arguments)
}
})
let list = [];
list.__proto__ = ArrayM;
list.push(123);
list.reverse();