I’m creating a simple class, but I get an error that I can’t understand. My class is very simple:
class Character { constructor(firstName, lastName, height){ this.firstName = firstName; this.lastName = lastName; this.height = height; } get firstName(){ return this.firstName; } set firstName(newName){ this.firstName = newName; } } const hombre = new Character("Pedro", "Blanch", 186); console.log(hombre.firstName);
But when I run in terminal with node script.js I always get an RangeError: Maximum call stack size exceeded error…. Where is the mistake?
Thanks!
Advertisement
Answer
In ECMAScript (which defines JavaScript), when you declare a class method with get
and/or set
keyword, a what is known as a property descriptor is created, on the prototype object. What you have in your class is absolutely equivalent to the following code:
Object.defineProperty(Character.prototype, "firstName", { get() { /* get first name */ } set(value) { /* set first name */ } });
Both accomplish exactly the same thing — accessing firstName
on objects of class Character
, no matter how or from where, will always invoke the “getter” method. Even in your constructor, this.firstName = firstName;
will invoke the “setter” method!
So, when you have an object of class Character
, let’s call it good_char
for example (var good_char = new Character
), and evaluate good_char.firstName
or this.firstName
in one of the class methods (including any of get
or set
methods), the get
function above will be called. That’s all well and good — that’s what you want, right?
If said function attempts to evaluate this.firstName
, that will naturally again cause the same get
function to be called — to get the value of the property, after all — a recursive call, without end — get
is then called again, encounters this.firstName
, calls itself, encounters this.firstName
… and so on ad infinitum.
That’s why the script interpreter tells you that it has run out of stack space — the stack is what tracks what calls what, and in your case get
calls itself recursively, exhausting stack space. The RangeError
refers to the stack growing out of allowed range.
All this behaviour basically should tell you that you cannot have a property that seemingly uses another property with the same name — they’re both the same property, accessed only through the getter method and assigned with the setter method. It doesn’t matter whether it’s accessed “from inside” the class method — as this.firstName
, or “from outside” — as good_char.firstName
— the get
will be called to, well, get the value of the property as per the descriptor that was created. You can’t get to any “actual”, “true” value unless you implement it through another property or otherwise, yourself. The descriptor you create with get
keyword or Object.defineProperty
does not hide any underlying value provided to you that you can access.
This may be confusing to you if you come from another programming language background where “getter” property paradigm is implemented differently.
Practically, if you insist, you will need to encapsulate something like _firstName
(doesn’t matter what you call it, it’s just a convention for this kind of programming pattern) behind your actual firstName
property that uses get
and/or set
, yourself. That is a bit of an anti-pattern, however — why do you need getter and setter methods for a property, when all they do is get some value (_firstName
) value and set it?
See also Object.defineProperty.