在之前的博文中,曾经总结过vue2数据监听的实现原理,本篇和之前其实也差不多,都是给对象添加get、set的存取器属性以达到数据拦截处理的目的.
为什么要给对象添加这种存取器的属性呢?是为了达到对对象中某些属性的读取进行监听的目的,方便在对对象进行读写时做一些特殊的处理.
下面通过代码来演示一下Object.defineProperty给对象添加属性的几种情况
1、给对象添加常规属性
let obj = { name: 'mike' }
Object.defineProperty(obj, 'age', {
configurable: true,
enumerable: true,
value: 18,
writeable: true
})
Object.keys(obj)
['name', 'age']
这里比较简单,只需要注意添加的属性几种描述就好
configurable: 是否允许该属性被重复定义还有是否允许该属性被删除
enumerable: 是否允许该属性被遍历
value: 该属性的值,如果为false那么将被初始化为undefined
writeable: 是否允许该属性被修改
除了value,其余三个默认都为false
2、给对象添加存取器属性
Object.defineProperty(obj, 'sex', {
get(){
return this.sex;
},
set(value){
this.sex = value;
}
})
obj.sex
//
Uncaught RangeError: Maximum call stack size exceeded
我们看到,我们给obj添加了sex的存取器属性,但是当我们对这个sex属性调用时发生了死循环的报错
这里需要注意的是,我们不能通过this.sex对sex属性进行读取和赋值,我们需要借助于一个临时的变量
修改一下上面的代码
let obj = { name: 'mike' }
let privateData = 'man' //临时变量
Object.defineProperty(obj, 'sex', {
get(){
return privateData;
},
set(value){
privateData = value
}
})
obj.sex // 'man'
记住这里读取新增的属性要借助临时变量哦!
3、value 和 writable 属性不能与 get、set共存
let myLike = 'fish'
Object.defineProperty(obj, 'like', {
configurable: true,
enumerable:true,
value: 'apple',
writeable: true,
get(){
return myLike;
},
set(value){
myLike = value
}
})
//Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
由上面的代码演示可以看到value 和 writable 属性不能与 get、set共存,两种情况只能二选一
4、构造函数的原型添加存取器属性
function Person(name){
this.name = name;
this.privateAge = 18;
}
Object.defineProperty(Person.prototype, 'age', {
configurable: true,
enumerable: true,
get(){
return this.privateAge;
},
set(value){
this.privateAge = value;
}
})
let person2 = new Person('lili')
let person1 = new Person('mike')
person2.age //18
person2.age = 28
person2.age // 28
person1.age //18
person1.age = 16
person1.age // 16
通过构造函数的原型可以给所有实例话的对象添加存取器属性,很方便
5、get、set没有成对出现的时候不能对该属性进行读写
function Person(name, sex){
this.name = name;
this.sex = sex;
console.log(this.age)
}
Object.defineProperty(Person.prototype, 'sex', {
configurable: true,
enumerable: true,
get(){
return 'man'
}
})
Object.defineProperty(Person.prototype, 'age', {
configurable: true,
enumerable: true,
set(value){
this.age = value
}
})
let person = new Person('mike', 'women')
person.sex //'man'
person.age = 18
//Uncaught RangeError: Maximum call stack size exceeded
1、我们在构造函数中添加了this.age和this.sex,并且想通过传参的形式来给this.sex初始化值
2、我们通过Object.defineProperty给构造函数的原型分别添加了sex和age的get和set的属性
3、通过Object.defineProperty新增的sex读属性,没有写,说明这个sex是只读的,所以this.sex的写不起作用
4、通过Object.defineProperty新增的age写属性中不能对使用this.age,否则会发生死循环
6、类中使用get、set
class Person {
constructor(name){
this.name = name;
this.privateAge = 18;
}
get age(){
return this.privateAge;
}
set age(value){
this.privateAge = value;
}
}
let person = new Person('mike')
person.age //18
person.age = 28
person.age //28
类中使用get、set其实和通过Object.defineProperty给构造函数的原型添加get、set是一样的,仅仅是写法上有些区别,类的形式看上去更为简洁方便
7、类中get、set不成对的时候,不能对这个属性进行赋值
class Person {
constructor(age){
this.age = age;
}
get age(){
return 18;
}
}
let person = new Person(28)
//Uncaught TypeError: Cannot set property age of #<Person> which has only a getter
不同于构造函数的是,在类中et、set不成对的时候,不能对这个属性进行赋值,连构造方法中的初始化都不行