提问人:Delfon 提问时间:9/14/2023 最后编辑:Peter SeligerDelfon 更新时间:9/21/2023 访问量:112
如何通过 Object.defineProperty 并利用类语法正确实现自己的属性 getter/setter?
How to correctly implement an own property getter/setter via Object.defineProperty and utilizing class syntax?
问:
这是我的代码
"use strict";
class Employee {
constructor(firstName, lastName, age, salary, gender) {
this._firstName = firstName;
this._lastName = lastName;
this._age = age;
this._salary = salary;
Object.defineProperty(this, "_gender", {
enumerable: true,
configurable: true,
set(value) {
if(typeof value !== "boolean"){
throw "Wrong type of data.";
}else{
this._newGender = value;
}
},
get() {
return this._newGender;
}
});
this._gender = gender;
this._retirementYears = 0;
}
retirement() {
if (this._gender === true) {
return this._retirementYears = 65 - this._age;
} else if (this._gender === false) {
return this._retirementYears = 60 - this._age;
} else {
console.error("Invalid gender.");
}
}
}
class Teacher extends Employee {
constructor(firstName, lastName, age, salary, gender, subject, yearsOfExperience) {
super(firstName, lastName, age, salary, gender);
this._subject = subject;
this._yearsOfExperience = yearsOfExperience;
}
}
const mathTeacher = new Teacher("John", "Smith", 35, 6000, true, "Math", 5);
console.log(mathTeacher);
在 console.log(mathTeacher) 之后,我得到了两个与性别相关的属性(_gender 和 _newGender)。我只想获得一个名为 _gender 的属性,但我不知道该怎么做。我尝试将赋值分配给this._gender但由于“超出最大调用堆栈大小”错误而不起作用
答:
有几种解决方案,但是什么让这个问题变得有趣,类实例的克隆是否适用于每种方法。
要克隆类实例,我们可以使用:
Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
事实证明,当在原型中定义属性时,只有最后一种方法适用于克隆。提供的克隆不会调用构造函数。调用构造函数是有问题的,因为在克隆时参数是未知的。
但我想说的是,我们需要在类中提供一些方法来使克隆更加灵活。因此,恕我直言,这里看到的克隆失败并不重要。clone()
因此,让我们开始滚动:
您可以使用私有属性(克隆失败!!):
class Employee {
#_newGender = false;
constructor(firstName, lastName, age, salary, gender) {
this._firstName = firstName;
this._lastName = lastName;
this._age = age;
this._salary = salary;
Object.defineProperty(this, "_gender", {
enumerable: true,
configurable: true,
set(value) {
if(typeof value !== "boolean"){
throw "Wrong type of data.";
}else{
this.#_newGender = value;
}
},
get() {
return this.#_newGender;
}
});
this._gender = gender;
this._retirementYears = 0;
}
retirement() {
if (this._gender === true) {
return this._retirementYears = 65 - this._age;
} else if (this._gender === false) {
return this._retirementYears = 60 - this._age;
} else {
console.error("Invalid gender.");
}
}
}
class Teacher extends Employee {
constructor(firstName, lastName, age, salary, gender, subject, yearsOfExperience) {
super(firstName, lastName, age, salary, gender);
this._subject = subject;
this._yearsOfExperience = yearsOfExperience;
}
}
const mathTeacher = new Teacher("John", "Smith", 35, 6000, true, "Math", 5);
try{
mathTeacher._gender = 'test';
} catch(e){
console.log(e);
}
console.log(mathTeacher);
const clone = Object.assign(Object.create(Object.getPrototypeOf(mathTeacher)), mathTeacher)
console.log(clone);
try{
clone._gender = 'test';
} catch(e){
console.log(e);
}
clone._gender = false;
console.log(mathTeacher, clone);
或者 make not enumerable (CLONING FAILED !!):_newGender
class Employee {
constructor(firstName, lastName, age, salary, gender) {
this._firstName = firstName;
this._lastName = lastName;
this._age = age;
this._salary = salary;
Object.defineProperty(this, "_newGender", {enumerable: false, value: false, writable: true});
Object.defineProperty(this, "_gender", {
enumerable: true,
configurable: true,
set(value) {
if(typeof value !== "boolean"){
throw "Wrong type of data.";
}else{
this._newGender = value;
}
},
get() {
return this._newGender;
}
});
this._gender = gender;
this._retirementYears = 0;
}
retirement() {
if (this._gender === true) {
return this._retirementYears = 65 - this._age;
} else if (this._gender === false) {
return this._retirementYears = 60 - this._age;
} else {
console.error("Invalid gender.");
}
}
}
class Teacher extends Employee {
constructor(firstName, lastName, age, salary, gender, subject, yearsOfExperience) {
super(firstName, lastName, age, salary, gender);
this._subject = subject;
this._yearsOfExperience = yearsOfExperience;
}
}
const mathTeacher = new Teacher("John", "Smith", 35, 6000, true, "Math", 5);
try{
mathTeacher._gender = 'test';
} catch(e){
console.log(e);
}
console.log(mathTeacher);
const clone = Object.assign(Object.create(Object.getPrototypeOf(mathTeacher)), mathTeacher)
console.log(clone);
try{
clone._gender = 'test';
} catch(e){
console.log(e);
}
clone._gender = false;
console.log(mathTeacher, clone);
实际上,您可以为此创建一些实用程序,并将实际价值保留在其范围内 (克隆失败!!):
const createValidatedProp = (obj, prop, cb, value) => {
let val = value;
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
set(value) {
if(!cb(value)){
throw "Wrong type of data.";
}else{
val = value;
}
},
get() {
return val;
}
});
};
class Employee {
constructor(firstName, lastName, age, salary, gender) {
this._firstName = firstName;
this._lastName = lastName;
this._age = age;
this._salary = salary;
createValidatedProp(this, '_gender', val => typeof val === 'boolean', gender);
this._retirementYears = 0;
}
retirement() {
if (this._gender === true) {
return this._retirementYears = 65 - this._age;
} else if (this._gender === false) {
return this._retirementYears = 60 - this._age;
} else {
console.error("Invalid gender.");
}
}
}
class Teacher extends Employee {
constructor(firstName, lastName, age, salary, gender, subject, yearsOfExperience) {
super(firstName, lastName, age, salary, gender);
this._subject = subject;
this._yearsOfExperience = yearsOfExperience;
}
}
const mathTeacher = new Teacher("John", "Smith", 35, 6000, true, "Math", 5);
try{
mathTeacher._gender = 'test';
} catch(e){
console.log(e);
}
console.log(mathTeacher);
const clone = Object.assign(Object.create(Object.getPrototypeOf(mathTeacher)), mathTeacher)
console.log(clone);
try{
clone._gender = 'test';
} catch(e){
console.log(e);
}
clone._gender = false;
console.log(mathTeacher, clone);
如果你想在原型中使用 getter/setter(就像 JS 类所做的那样(但属性不可枚举)),请使用符号来保留实际值(符号属性不可枚举):
Symbol 是一个内置对象,其构造函数返回一个符号基元(也称为 Symbol 值或简称为 Symbol),该基元保证是唯一的。符号通常用于向对象添加唯一属性键,这些键不会与任何其他代码可能添加到对象的键冲突,并且这些键对其他代码通常用于访问对象的任何机制都是隐藏的。这使得一种形式的弱封装,或一种弱形式的信息隐藏。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
(克隆正常):
const createValidatedProp = (obj, prop, cb) => {
let name = Symbol(prop);
Object.defineProperty(obj, prop, {
enumerable: true,
configurable: true,
set(value) {
if(!cb(value)) throw "Wrong type of data.";
this[name] = value;
},
get() {
return this[name];
}
});
};
class Employee {
constructor(firstName, lastName, age, salary, gender, test) {
this._firstName = firstName;
this._lastName = lastName;
this._age = age;
this._salary = salary;
this._retirementYears = 0;
this._gender = gender;
this._subject = test;
}
retirement() {
if (this._gender === true) {
return this._retirementYears = 65 - this._age;
} else if (this._gender === false) {
return this._retirementYears = 60 - this._age;
} else {
console.error("Invalid gender.");
}
}
}
createValidatedProp(Employee.prototype, '_gender', val => typeof val === 'boolean');
createValidatedProp(Employee.prototype, '_subject', val => typeof val === 'string');
class Teacher extends Employee {
constructor(firstName, lastName, age, salary, gender, subject, yearsOfExperience) {
super(firstName, lastName, age, salary, gender, subject);
this._subject = subject;
this._yearsOfExperience = yearsOfExperience;
}
}
const mathTeacher = new Teacher("John", "Smith", 35, 6000, true, "Math", 5);
try{
mathTeacher._gender = 'test';
} catch(e){
console.log(e);
}
console.log(mathTeacher);
const clone = Object.assign(Object.create(Object.getPrototypeOf(mathTeacher)), mathTeacher)
console.log(clone);
try{
clone._gender = 'test';
} catch(e){
console.log(e);
}
clone._gender = false;
console.log(mathTeacher, clone);
评论
undefined
undefined
从我上面的评论......
“OP 对
Employee
自己的_gender
属性有什么期望,该属性是通过get
/set
实现的,其中 setter 确实显式分配给额外引入的自己的 _newGender 属性,并且 getter 总是返回自己的
_newGender
属性的值?Employee
的整个实现已经存在缺陷。
可能的重构...
- 可以为每个构造函数引入一个配置对象,
- 可能会摆脱基于伪私有下划线的前缀注释,
- 应该正确获取 getter/setter 实现,
gender
- 鼓励正确执行“退休前年限”的计算。
class Employee {
constructor({
// ensure minimum default values.
firstName = '',
lastName = '',
age = 0,
salary = 0,
gender = false,
}) {
Object.defineProperty(this, 'gender', {
set(value) {
if (typeof value !== 'boolean') {
throw 'A boolean data type needs to be assigned.';
} else {
// assignement to the enclosed/local `gender` variable.
gender = value;
}
},
get() {
// return value of the enclosed/local `gender` variable.
return gender;
},
enumerable: true,
});
Object.assign(this, {
firstName, lastName, age, salary, gender,
});
}
// - either rename and correctly implement the `retirement` method
// - or implement `yearsUntilRetirement` via getter and setter.
getYearsUntilRetirement() {
if (this.gender === true) {
return 65 - this.age;
} else {
return 60 - this.age;
}
}
}
class Teacher extends Employee {
constructor({
// ensure minimum default values.
subject = '',
yearsOfExperience = 0,
// assume additional base configuration.
...baseConfig
}) {
super(baseConfig);
Object.assign(this, {
subject, yearsOfExperience,
});
}
}
const mathTeacher = new Teacher({
firstName: 'John',
lastName: 'Smith',
age: 35,
salary: 6000,
gender: true,
subject: 'Math',
yearsOfExperience: 5,
});
console.log({
mathTeacher,
});
console.log(
'mathTeacher.getYearsUntilRetirement() ...',
mathTeacher.getYearsUntilRetirement(),
);
mathTeacher.gender = false;
console.log({
mathTeacher,
});
console.log(
'mathTeacher.getYearsUntilRetirement() ...',
mathTeacher.getYearsUntilRetirement(),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
评论
gender
评论
Employee
_gender
get
set
_newGender
_newGender
Employee
if(typeof value !== "boolean") throw …;