如何通过 Object.defineProperty 并利用类语法正确实现自己的属性 getter/setter?

How to correctly implement an own property getter/setter via Object.defineProperty and utilizing class syntax?

提问人:Delfon 提问时间:9/14/2023 最后编辑:Peter SeligerDelfon 更新时间:9/21/2023 访问量:112

问:

这是我的代码

"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但由于“超出最大调用堆栈大小”错误而不起作用

JavaScript 构造函数 getter-setter defineproperty

评论

0赞 Peter Seliger 9/20/2023
OP 对 自己的属性有什么期望,该属性是通过 / 实现的,其中 setter 确实显式分配给额外引入的 own 属性,并且 getter 总是返回 own 属性的值?已经的整个实现是有缺陷的。Employee_gendergetset_newGender_newGenderEmployee
0赞 Bergi 9/21/2023
你为什么要使用二传手?只需在构造函数中做正确的操作if(typeof value !== "boolean") throw …;
0赞 Peter Seliger 9/25/2023
@Delfon......OP 可能会查看我最近提供的答案,它显示了一种以比 OP 的原始代码更不破碎的方式实现 OP 示例的可能方法。尽管收到了反对票,但还是鼓励 OP 尝试一下。

答:

0赞 Alexander Nenashev 9/14/2023 #1

有几种解决方案,但是什么让这个问题变得有趣,类实例的克隆是否适用于每种方法。

要克隆类实例,我们可以使用:

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);

评论

0赞 Delfon 9/14/2023
为什么会有“#_newGender = false”?
0赞 Alexander Nenashev 9/14/2023
@Delfon只是用一些值初始化它,你可以保留它。我不喜欢,危险undefinedundefined
0赞 Peter Seliger 9/21/2023
@AlexanderNenashev......在最初的问题中是否引入了“克隆”方面?指向OP设计失败的实际答案在哪里?
0赞 Alexander Nenashev 9/21/2023
@PeterSeliger我想研究的主题的有趣方面,学习 JS
1赞 Peter Seliger 9/21/2023
@AlexanderNenashev......“只是我想调查的话题的有趣方面”。但是,回答者回答问题并指出解决他问题的方法,这难道不是最崇高的任务吗?
-1赞 Peter Seliger 9/21/2023 #2

从我上面的评论......

“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; }

评论

0赞 Alexander Nenashev 9/21/2023
与我的第三种方法没有什么不同,当你将 prop 值保留在私有范围内时
0赞 Peter Seliger 9/21/2023
@AlexanderNenashev......实际上,唯一的相似之处是使用闭包,它封装了设置/获取自身属性的局部变量/状态。gender
0赞 Alexander Nenashev 9/21/2023
无论如何,我只是在 util 函数中将逻辑移到了外面