I've tried trouble shooting this with essentially the exact same issue on codecademy to no avail. I'm trying to get my javascript setter to check whether the argument I am passing in is a number, and if the conditions are satisfied set it to the class property, else return an error string.
I can't understand why this isn't working and I've spent several long hours on it already, any help would be greatly appreciated!
Code:
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
get name() {
return this._name;
}
get age() {
return this._age;
}
set age(num) {
if (num.isNaN()) {
console.log('error!')
} else {
this._age = num
}
}
}
let human = new Person('Armand', 'string');
console.log(human);
If you want to fire the setter function inside the constructor, you should not use the backing field property (this._age) inside the constructor. Remove the underscore. (Source)
Furthermore, string does not have the isNan() method rather, it takes the variable as an argument like the code below. (Source)
If you want a begginer's guide to Javascript ES6 Class Syntax, you should check this out. Thanks :)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
get name() {
return this._name;
}
get age() {
return this._age;
}
set age(numeric) {
if (isNaN(numeric)) {
console.log('error!')
} else {
this._age = numeric
}
}
set name(newName) {
this._name = newName;
}
}
let human = new Person('Armand', 'string');
console.log(human);
Related
I've read that the underscore in js is an acceptable naming character with no additional functionality, used to signify something is private. My question is how does this work then?
class User {
constructor(name) {
this.name = name
}
get name() {
return this._name
}
set name(value) {
if (value.length < 4) {
alert("Name is too short")
return
}
this._name = value
}
}
let user = new User("Kiki")
alert(user.name)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
</body>
</html>
The variable is not defined the first time with the underscore, how does JavaScript know what I'm referring to?
Also if I remove the underscores I get an error that
InternalError: too much recursion
I don't understand why? If the underscore has no functionality, shouldn't it work the same without the underscores?
Your working version has two properties:
_name, a data property you create in the constructor
name, an accessor property you create on the class prototype via get name() { /*...*/ } (and set name(value) { /*...*/ })
In this code:
let user = new User("Kiki")
alert(user.name)
...when you use user.name, you're using the accessor property's getter function (get name), which returns the value of this._name.
If you remove the _, you have only one property, which is the accessor property, and assigning to this.name in the constructor calls the accessor's setter function, which (in that version) would do this.name = ____, which would call the setter function, which would do this.name = ____, which would call the setter function...
If you're going to have that public name property that does validation on the value you assign to it, you need a place to keep the value the accessor accesses. That can be another public property with a different name (_name), which is fine though easily bypassed; or in modern code you might use a private instance field instead:
class User {
#name; // Private instance field declaration
constructor(name) {
this.name = name;
}
get name() {
return this.#name;
}
set name(value) {
if (value.length < 4) {
console.log("Name is too short");
return;
}
this.#name = value;
}
}
let user = new User("Kiki");
console.log(user.name);
Side note: I'd make the error condition throw an Error, not just output a message, not least so that new User("x") doesn't leave the name with the default value undefined:
set name(value) {
if (value.length < 4) {
throw new Error(`Name is too short, expected at least 4 chars, got ${value.length}`);
}
this.#name = value;
}
In a comment you've asked:
...if I put this._name=name in the constructor it works as well. Would that be better practice?
That would mean that you wouldn't check the length of the name passed to the constructor. But your instinct is good! In class-based OOP circles, best practice is usually not to call setters (or other public methods) from constructors, because the setter/method may be overridden in a derived class, and the derived class may expect that the setter is working with a fully-initialized instance. But when you all a setter from the constructor, the instance may not be fully initialized yet.
In your specific case it doesn't really matter, there's only one property (conceptually), but the usual way to avoid the issue with overridden setters/methods is to use a private function that does the work of the setter, and use that function both in the setter and the constructor:
class User {
#name;
constructor(name) {
this.#setName(name);
}
#setName(value) { // <== Private instance method
if (value.length < 4) {
throw new Error(`Name is too short, expected at least 4 chars, got ${value.length}`);
}
this.#name = value;
}
get name() {
return this.#name;
}
set name(value) {
this.#setName(value);
}
}
try {
let user = new User("Kiki");
console.log(user.name);
} catch (error) {
console.error("ERROR, the first call should work");
}
try {
let user = new User("X");
console.log(user.name);
console.error("ERROR, the second call should fail");
} catch (error) {
console.log(`Second call failed as expected: ${error.message}`);
}
try {
let user = new User("Kiki");
console.log(user.name);
user.name = "x";
console.error("ERROR, the third call should fail");
} catch (error) {
console.log(`Third call failed as expected: ${error.message}`);
}
Since a private method can't be overridden in a derived class, it's okay to call it from the constructor.
The underscore is just a commonly-used way of remembering that the property is private. You can use any variable name you want.
Just remember that there are two identifiers for the name, one is for internal use and one is for external use. They can't be exactly the same.
class User {
constructor(name) {
this.privateName = name
}
get name() {
return this.privateName
}
set name(value) {
if (value.length < 4) {
alert("Name is too short")
return
}
this.privateName = value
}
}
let user = new User("Kiki")
console.log(user.name)
That being said, in the latest javascript you can (should?) use the # to make private properties. This makes sure they are actually private (not reachable from outside the class)
class User {
#name
constructor(name) {
this.#name = name
}
get name() {
return this.#name
}
set name(value) {
if (value.length < 4) {
alert("Name is too short")
return
}
this.#name = value
}
}
let user = new User("Henk")
console.log(user.name)
And because we used # the private variable actually can't be reached:
console.log(user.#name) // ERROR
How can I change the way String or Number casts a class?
For example, I have the class User:
class User {
constructor(name, age) {
this._name = name;
this._age = age;
}
}
How do I convert a class object to String and get the name attribute back when I convert it to String. And if I want to convert the object of the class to Number, it returns the age attribute.
Like this:
const user = new User('Kevin', 23);
console.log(String(user)) // Kevin
console.log(Number(user)) // 23
Thanks!
Use toString and valueOf methods.
class User {
constructor(name, age) {
this._name = name;
this._age = age;
}
toString() { return this._name; }
valueOf() { return this._age; }
}
const user = new User('Kevin', 23);
console.log(String(user)) // Kevin
console.log(Number(user)) // 23
I want to know why?
It doesn't look like this reduces complexity.
String(user);
Number(user);
user.string;
user.number;
user.name;
user.age;
This question already has answers here:
JavaScript classes with getter and setter cause RangeError: Maximum call stack size exceeded
(5 answers)
Closed 3 years ago.
I know that using an underscore is just a convention to define private variables in JavaScript. But I came across a use case [while using a class] where use of _ seems mandatory to make the code work! My question is how _ is used under the hood by get and set.
The below code throws an error:
RangeError: Maximum call stack size exceeded
class User {
constructor(name) {
this.name = name;
}
get name() {
return this.name;
}
set name(val) {
this.name = val;
}
}
let user = new User("Jhon");
console.log(user.name);
Now, if I use _ the code works!
class User {
constructor(name) {
this.name = name;
}
get name() {
return this._name; // Added "_" here
}
set name(val) {
this._name = val; // Added "_" here
}
}
let user = new User("Jhon");
console.log(user.name);
Your first snippet uses the same name for the getter/setter as the property you try to assign to. So, in the constructor, when you do
this.name = name;
you are invoking the name setter, which does:
this.name = val;
again invoking the name setter, which recursively calls itself until the stack overflows.
Using a different variable name for the actual property the data is stored in (compared to the getters/setters) allows for the code to work as intended. It doesn't have to be prefixed with an underscore - pretty much anything other than the same name used by the getters/setters will work.
class User {
constructor(name) {
this.name = name;
}
get name() {
return this.actualProperty;
}
set name(val) {
this.actualProperty = val;
}
}
let user = new User("Jhon");
console.log(user.name);
The _ before a property name is generally meant to indicate that the property is meant to be private, and that only the class itself should access it, but it's no guarantee - users of the class are still free to reference user._name if they wish. If you want actual private data for each instance, you should define the class in a closure with a WeakMap that holds the private data:
const User = (() => {
const data = new WeakMap();
return class User {
constructor(name) {
this.name = name;
}
get name() {
return data.get(this);
}
set name(val) {
data.set(this, val);
}
}
})();
let user = new User("Jhon");
console.log(user.name);
Just look at this piece of code logically:
get name() {
return this.name
}
You read object.name. To return a value, the get name() getter reads this.name, which, in turn, resolves to get name(). And now, welcome to the infinite loop.
Hence, you need a separate variable name (to store the actual content of name) than the getter's name. That would be a private variable, and it has become a convention to prepend an underscore in these cases.
The _ affix is commonly used for private properties.
You use private properties in addition to getters and/or setters when you want to be able to control how and when you can update a property, or add side effects to those actions.
You should also have a private property declaration in your class
class User {
private _name; // <-- here
constructor(name) {
this.name = name;
}
get name() {
return this._name;
}
set name(val) {
this._name = val;
}
}
I want to create a setter in JS. But there is something wrong with my code, and this is my code:
class test {
constructor(str) {
this.name = str;
}
set name(str) {
this.sayHi();
}
sayHi() {
let temp = this.name;
console.log(`My name is ${temp}`)
}
}
let a = new test('bill') //My name is undefined
a.sayHi() //My name is undefined
why does it console undefined in this example?how to make it work?
Your setter needs to store the value somewhere; you'll also need a getter to get the value from that place.
Here's a simple example storing the value in another property:
class Test {
constructor(str) {
this._name = str; // ***
// (You might use `this.name = str` here, setters are sometimes
// considered an exception to the "don't call methods in the
// constructor" rule)
}
set name(str) {
this._name = str; // ***
}
get name() { // ***
return this._name; // ***
} // ***
sayHi() {
let temp = this.name;
console.log(`My name is ${temp}`)
}
}
let a = new Test('bill') //My name is undefined
a.sayHi() //My name is undefined
Of course, if you're going to do that, it doesn't make a lot of sense to have a setter, but that's getting a bit afield of the question...
Note: I changed the name of your class to Test (instead of test). The overwhelming convention in JavaScript is that class names (really constructor function names) are initially-capitalized.
Try this instead:
class test {
constructor(str) {
this.name = str;
}
set name(str) {
this._name = str
}
sayHi() {
let temp = this.name;
console.log(`My name is ${temp}`)
}
get name() {
return this._name
}
}
Could you explain why I get
Uncaught RangeError: Maximum call stack size exceeded
in this example. What's the sequence of actions?
"use strict";
let myClass = class myClass{
constructor(name){
this.name = name;
}
get name() { return this.name; }
set name(name){ this.name = name; }
}
let myObj = new myClass("John");
You're calling the setter from the setter, infinitely looping.
set name(name) {
this.name = name; // <-- ⛔ `this.name` calls the `set`ter again
}
You should use a differently named backing variable of some sort. Unfortunately the "TC39 Private Fields" proposal for JS is not finalized so they will be public, and a naming convention is needed for now.
Here's a modern example:
class Person {
_name = ""; // 'private' by convention, see also: https://github.com/tc39/proposal-class-fields#private-fields
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
constructor(name) {
this.name = name;
}
}
Or following the Question's structure:
"use strict";
let myClass = class myClass {
constructor(name) {
this.name = name;
}
get name() {
return this._name;
}
set name(name) {
this._name = name;
}
}
let myObj = new myClass("John");
console.log(myObj);
To my surprise it's not trivial to have variables private to a class.