Before we begin, I'm running on a webpack-dev-server, and I'm building a to do list app. My goal is to improve my understanding of how accessors behave so that I can standardizing my writing style with classes.
Now the snippet below is how I set up my class User:
export class User {
name; //public field
constructor(name, id = crypto.randomUUID(), xp = 0) {
this.id = id;
this.name = name;
Object.assign(this, createExpHolder(xp), createProjectHolder(), createTaskHolder());
}
get name() {
return this.name;
}
set name(name) {
this.name = name;
}
}
const user = new User("StackOverflow");
console.log(user.name); //prints StackOverflow and no errors
At first glance, you would have thought that this code will return an error due to stack overflow. And I'm surprised it didn't. Whenever I mutate the instance's name it doesn't trigger an error, and it works fine! Is it because of the dev server? Does webpack handle it? Is this a quirk of the JavaScript engine?
I've done my research for finding explanation in MDN, and bunch of AIs to explain it for me but the solution/response they gave was to use private field or the underscore naming convention.
This is a problem for me because I want to standardize my writing style with classes. And if I've grown accustomed to this setup, I might have a technical debt in the future.
You're not running into a StackOverflow error, because you declare the public field name, which is created on the instance before the constructor body runs. So when you do this.name = name in the constructor, you're just assigning to that own data property. Because the instance already has a data property named name, it masks the get name() / set name() accessors that live on the prototype. If you comment out / remove the declaration, you will indeed run into an error:
export class User {
constructor(name, id = crypto.randomUUID(), xp = 0) {
this.id = id;
this.name = name;
Object.assign(this, 0, 0, 0);
}
get name() {
return this.name;
}
set name(name) {
this.name = name;
}
}
const user = new User("StackOverflow");
console.log(user.name);
// Line 15: Uncaught RangeError: Maximum call stack size exceeded
// at set name (<anonymous>:15:19)
// [...]
I suggest declaring a private name field using #name
class User {
#name; //private field
constructor(name, id = crypto.randomUUID(), xp = 0) {
this.id = id;
this.#name = name;
}
get name() {
return this.#name;
}
/*set name(name) {
this.#name = name;
}*/
}
const user = new User("StackOverflow");
console.log(user.name);
user.name = "Brentspine" // Only works when setter is declared, read below
console.log(user.name);
In strict mode, trying to use user.name = "..." without a defined setter, it throws a TypeError; in non-strict mode it’s silently ignored.
When trying to access the name using the # like in the following snippet, you would get a SyntaxError
console.log(user.#name);
user.#name = "..."
While trying to to access the field user["#name"] would return undefined, as it would treat #name as its own property name different to name
console.log(user["#name"]); // Would log undefined
user["#name"] = "Brentspine"
console.log(user["#name"]); // Would log "Brentspine"
console.log(user.name); // Would log initial value
Btw: Private fields will not appear in Object.keys, for…in or JSON.stringify