Q1With a getter that returns 30 - this.reserved, what do you get if you set reserved to 20 and then read the getter?
Getters and Setters — Running Code When a Property Is Read or Assigned
Getters and setters run code when a property is read or assigned. Covers computed properties, validating assigned values, going through a setter from the constructor, and read-only properties.
Say a seminar sign-up page shows the number of seats left as "capacity − sign-ups". If you calculate the seats left when you create the instance and store it, the display won't change when more people sign up later. And if someone assigns a capacity smaller than the number of sign-ups, nothing stops it.
This article covers getters and setters, which run code when a property is read or assigned.
Recalculating on Every Read — get and Computed Properties
If you calculate the seats left as "capacity − sign-ups" in the constructor and store it, the seats left stay at the value from creation time even after you change the number of sign-ups. Making it a method lets you recalculate, but then callers have to remember to leave the parentheses off for the capacity and add them for the seats left.
You write a getter (a method in a class with get in front, which runs when the property is read) with no parameters, like get remainingSeats() { ... }, and reading the property gives you whatever it returns.
A property like remainingSeats, which is calculated from other properties and returned, is called a computed property.
class Seminar {
reserved = 0; // Number of sign-ups
constructor(capacity) {
this.capacity = capacity; // Capacity
}
// Calculates from the current capacity and sign-ups every time it's read
get remainingSeats() {
return this.capacity - this.reserved;
}
}
const seminar = new Seminar(30);
console.log(seminar.remainingSeats); // 30 (read without parentheses)
// Read it again after 12 people sign up
seminar.reserved = 12;
console.log(seminar.remainingSeats); // 18
The 30 in the top row was calculated once at new time and stored. The getter in the bottom row stores nothing and runs the return expression every time you read seminar.remainingSeats, so no matter how many times you change reserved, you don't need to add a line that recalculates the seats left.
Calling a Getter with Parentheses Throws a TypeError
You write a getter as a method, but callers don't add parentheses. If you write seminar.remainingSeats(), you're calling the returned 18 as a function, which throws TypeError: seminar.remainingSeats is not a function.
Checking Assigned Values — set and Validation
As a seminar gets closer, the venue might let you raise the capacity from 30 to 40. You could write a check with if and throw in the constructor, but it only runs at new time. If you later assign seminar.capacity = 5, which is less than the 12 sign-ups, nothing stops it, and the seats left show as -7.
A setter (a method you write in a class with set in front, which runs when the property is assigned) receives the assigned value as its parameter value. You store the value itself under a different name, such as _capacity, and return it from a getter with the property's name, get capacity.
A leading _ is a convention that means "don't touch this directly from outside".
class Seminar {
constructor(capacity, reserved) {
this.reserved = reserved; // Number of sign-ups
this._capacity = capacity; // The capacity value itself goes in _capacity
}
get capacity() { return this._capacity; }
set capacity(value) { // The assigned value arrives in value
if (value < this.reserved) {
throw new Error(`Capacity must be at least the ${this.reserved} sign-ups: ${value}`);
}
this._capacity = value;
}
}
const seminar = new Seminar(30, 12);
seminar.capacity = 40; // set capacity runs and stores 40
console.log(seminar.capacity); // 40 (get capacity runs)
seminar.capacity = 5; // Error: Capacity must be at least the 12 sign-ups: 5
=, set runs; when it's read anywhere else, get runs. A value that fails the check never reaches _capacity.Line ③ throws, so unless you wrap it in try, the lines after it don't run. As long as you assign to capacity, the value goes through the setter, but if you write seminar._capacity = 5 directly, it skips the check. The next article covers how to prevent that.
Assigning to the Same Name Inside a Setter Throws a RangeError
If you write this.capacity = value; inside set capacity(value), that assignment calls the setter again, which assigns again, and so on until it throws RangeError: Maximum call stack size exceeded. Store the value under a different name, such as _capacity.
Going Through the Setter at Creation Too — Assigning in the constructor
The Seminar above has its constructor store the value directly in _capacity, so new Seminar(5, 12) creates an instance without any check. If you also write the same if in the constructor, you have to fix two places every time the condition changes, and if you forget one, new and assignment give different results.
If you assign to the name without _ inside the constructor, as in this.capacity = capacity, the setter runs for that line too. The check lives in one place, the setter, and values passed to new and values assigned later are checked against the same condition.
class Seminar {
constructor(capacity, reserved) {
this.reserved = reserved; // Store the sign-ups used by the check first
this.capacity = capacity; // The name has no _, so set capacity runs
}
get capacity() { return this._capacity; }
set capacity(value) {
if (value < this.reserved) {
throw new Error(`Capacity must be at least the ${this.reserved} sign-ups: ${value}`);
}
this._capacity = value;
}
}
try {
new Seminar(5, 12); // new also goes through the setter's check
} catch (error) {
console.log(error.message); // Capacity must be at least the 12 sign-ups: 5
}
new Seminar(5, 12)— calls the constructor- The setter's exception comes back up to here, and no instance is returned
this.reserved = 12— stores the sign-ups before the checkthis.capacity = 5— the name has no_, so it calls set capacity
valueis 5 andthis.reservedis 125 < 12, sothrowruns — nothing goes into_capacity
this.capacity = 5 line calls set capacity. The exception leaves the constructor and reaches try.If you move this.reserved = reserved below this.capacity = capacity, this.reserved is undefined when the setter runs. In a numeric comparison, undefined becomes NaN, and 5 < NaN is false, so the instance is created with a capacity of 5.
Blocking Assignment — Properties with Only a Getter
The seats left are determined by the capacity and the sign-ups. If the page's code can write seminar.remainingSeats = 100, the page shows 100 seats left for a capacity of 30 with 12 sign-ups, and the three values no longer add up.
A property with only a getter and no setter of the same name is read-only (it can be read but not assigned). In class bodies and in strict mode (a mode that turns some mistakes into exceptions), which this runtime uses, the line that assigns to it throws a TypeError. Otherwise, the assignment is silently ignored.
class Seminar {
constructor(capacity, reserved) {
this.capacity = capacity;
this.reserved = reserved;
}
get remainingSeats() { return this.capacity - this.reserved; } // No setter
}
const seminar = new Seminar(30, 12);
try {
seminar.remainingSeats = 100; // Assigning to a name with no set
} catch (error) {
console.log(error.name); // TypeError
}
console.log(seminar.remainingSeats); // 18 (same as before the assignment)
// To change the seats left, change the sign-ups they're based on
seminar.reserved = 20;
console.log(seminar.remainingSeats); // 10
The bottom row throws because a name with only a getter has nowhere to put an assigned value, and no new key is created on the instance either. The table below lists the approaches used in this article's Seminar, along with a method for comparison.
| Approach | How Callers Use It | Good For |
|---|---|---|
| Plain property (reserved) | seminar.reserved = 20 stores it as-is | Values that don't need checks |
| Getter only (remainingSeats) | Read-only: seminar.remainingSeats | Values computed from others |
| Getter and setter (capacity) | seminar.capacity = 40 is checked | Values to check on every assignment |
| Method (e.g., reserve(count)) | Called with parentheses and arguments | Results that depend on arguments |
Knowledge Check
Answer each question one by one.
Q2If set capacity contains this.capacity = value; and you assign 40, what happens?
Q3What happens if you assign 100 to seminar.remainingSeats, which has only a getter?