it's my code
"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);
After console.log(mathTeacher) I get two properties relating to gender (_gender and _newGender). I want to get only one property called _gender and I have no idea how to do it. I tried to assign assign value to this._gender but it doesn't work due to "Maximum call stack size exceeded" error
There are several solution but what made the question interesting, whether cloning of an class instance works for each approach.
To clone an class instance we could use:
Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
As turned out only the last approach works with cloning when the property is defined in the prototype. The provided cloning doesn't call the constructor. And calling the constructor is problematic since the arguments are unknown at the time of cloning.
But I would say that we need some clone()
method in a class to make cloning more flexible. So the cloning failures seen here aren't critical imho.
So let's start it rolling:
You can use a private property (CLONING FAILED !!!):
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);
Or make _newGender
not enumerable (CLONING FAILED !!!):
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);
Actually you could create some utility for this and keep the real value in its scope (CLONING FAILED !!!):
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);
If you want to use the getter/setter in the prototype (like a JS class would do (but the property isn't enumerable)) use a symbol to keep the real value (symbol properties aren't enumerable):
Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
(CLONING OK):
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);