先了解一下Object.defineProperty()方法
Object.defineProperty()
静态方法会直接在一个对象上定义一个新属性,或修改其现有属性,并返回此对象。
javascript
//obj:要定义的对象
//prop:一个字符串或 Symbol,指定了要定义或修改的属性键。
//descriptor:要定义或修改的属性的描述符,换句话说是一个属性描述对象
/*
configurable:代表该属性是否能被删除,默认值为:false
enumerable:代表该属性是否能被遍历,默认值为:true
value:代表该属性的值,默认值为:undefined
writable:代表该属性是否可以重写,默认值为false
get:是一个函数,用作属性 getter 的函数,如果没有 getter 则为 undefined
set:是一个函数,用作属性 setter 的函数,如果没有 setter 则为 undefined
*/
//返回值:传入函数的对象,其指定的属性已被添加或修改。
Object.defineProperty(obj, prop, descriptor)
/*
此外,Object.defineProperty() 使用 [[DefineOwnProperty]] 内部方法,
而不是 [[Set]],因此即使属性已经存在,它也不会调用 setter。
*/
javascript
const object1 = {};
Object.defineProperty(object1, 'property1', {
value: 42,
writable: false,
});
object1.property1 = 77;
// Throws an error in strict mode 因为writable属性为false,不可重写该属性,所以值还是42
console.log(object1.property1);
// 42
目的:
通过Object.defineProperty()方法遮蔽类中的实例属性,并进行显示控制与操作
应用举例
如下代码中:在实例化之前通过Object.defineProperty该方法给Person类的原型对象上添加了age属性且匹配了对应的getter和setter,此时输出年龄没有问题
javascript
class Person {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
//测试数据
let value = 20;
// 使用defineProperty给Person原型添加age属性,并配置对应的get与set
Object.defineProperty(Person.prototype, 'age', {
get() {
return value
},
set(val) {
value = val
}
})
const p1 = new Person('张三', 18)
console.log(p1.age) //18
console.log(Person.prototype.age)//18
需要注意:
第一次new Person时会调用Person类的constructor方法,在该方法内部会为实例对象赋初始值,当执行 this.age = age 时,首先这是在为实例对象添加age属性且赋初始值,但是js引擎会现在实例自身查找是否有age属性,如果没有会去Person类的原型对象上查找age属性,因为在实例化之前通过Object.defineProperty该方法给Person类的原型对象上添加了age属性且匹配了对应的getter和setter,所以age属性对应的setter就会执行,也就是说age属性被添加在了Person类的原型对象中,并非是Person类的实例
继续实例化,并分别通过实例对象输出age属性
javascript
const p1 = new Person('张三', 18);
const p2 = new Person('李四', 30);
console.log(p1.age) //30
console.log(p2.age) //30
发现p1.age和p2.age属性值都是一样,有一种第二次实例化时传递进去的age参数覆盖掉了p1.age属性
具体原因:
在实例化之前通过Object.defineProperty该方法给Person类的原型对象上添加了age属性且匹配了对应的getter和setter,之后在实例化时,为实例对象设置age属性时,其实都是操作的Person类原型对象上的age属性,所以后者会覆盖前者
交换顺序:
先实例化然后在通过Object.defineProperty该方法给Person类的原型对象上添加了age属性且匹配了对应的getter和setter
javascript
"use strict";
class Person1 {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
//先实例化对象
const p1 = new Person1("Tom", 20);
const p2 = new Person1("Tom", 40);
//测试数据
let value1 = 20;
//在通过Object.defineProperty方法为Person原型上添加age属性
Object.defineProperty(Person1.prototype, "age", {
get() {
return value1;
},
set(val) {
value1 = val;
},
//控制是否可以被枚举和删除
enumerable: true,
configurable: true,
});
//输出不同实例的age属性
console.log(p1.age); //20
console.log(p2.age); //40
此时每个实例都有age属性,且age属性对应的值都是正常的,互不影响,因为age属性是存在于实例对象自身而不是Person类的原型对象中
但是会有一点瑕疵:Object.defineProperty的作用就没发挥出来,并没有遮蔽实例自身的age属性
完美写法:
关键:访问器中的this指向Person类的实例对象,所以可以通过this来区分,分别为不同的实例对象赋值
javascript
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
Object.defineProperty(Person.prototype, "age", {
//访问器中的this指向Person类的实例对象,也就可以区分开来了
//访问器中不能写["age"],因为会造成死循环,导致栈溢出
get() {
return this["__age"];
},
set(val) {
this["__age"] = val;
},
});
const p1 = new Person("Tom", 20);
const p2 = new Person("Tom", 40);
console.log(p1);
console.log(p2);
如果在访问器中写成 this["age"] 这样,就会出现下面的问题