Getters and Setters
A getter is a function that runs when you read a property; a setter runs when you write to it. From the outside the property looks like a plain field, but you can compute it, validate it, or trigger a side effect on each access. They are the cleanest way to migrate a value into a calculation without changing every caller.
The class syntax
class Circle {
constructor(radius) {
this.radius = radius;
}
get diameter() {
return this.radius * 2;
}
set diameter(value) {
this.radius = value / 2;
}
}
const c = new Circle(5);
console.log(c.diameter); // 10 — getter
c.diameter = 30; // setter
console.log(c.radius); // 15No parentheses on read or write — the property is the computed value to callers. That is the point: a refactor from public field to derived value does not break any caller.
Validation in setters
Setters are the natural place to enforce invariants. Throw, clamp, or log — whatever the design needs.
class Temperature {
#celsius;
constructor(celsius) {
this.celsius = celsius; // goes through the setter
}
get celsius() { return this.#celsius; }
set celsius(value) {
if (typeof value !== "number" || Number.isNaN(value)) {
throw new TypeError("celsius must be a number");
}
if (value < -273.15) {
throw new RangeError("below absolute zero");
}
this.#celsius = value;
}
}
const t = new Temperature(20);
console.log(t.celsius); // 20
try {
t.celsius = -300;
} catch (e) {
console.log("rejected:", e.message);
}The private field #celsius (covered in Private Fields) stores the real number. The getter and setter become the only way in or out.
Getters with no setter
A getter without a matching setter creates a read-only property. Assignments are silently ignored in sloppy mode, and throw in strict mode (which classes always use).
class Rectangle {
constructor(w, h) { this.width = w; this.height = h; }
get area() { return this.width * this.height; }
}
const r = new Rectangle(3, 4);
console.log(r.area); // 12
try {
r.area = 99;
} catch (e) {
console.log("blocked:", e.message);
}Outside of classes — in object literals
Getters and setters work in plain object literals too. Same get and set keywords, same semantics.
const profile = {
firstName: "Ada",
lastName: "Lovelace",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(" ");
},
};
console.log(profile.fullName); // 'Ada Lovelace'
profile.fullName = "Grace Hopper";
console.log(profile.firstName); // 'Grace'Defining accessors with defineProperty
If you cannot use the literal syntax — for example, when adding an accessor to an existing object — Object.defineProperty accepts a get/set descriptor.
const obj = { _count: 0 };
Object.defineProperty(obj, "count", {
get() { return this._count; },
set(value) {
if (value < 0) throw new RangeError("negative");
this._count = value;
},
enumerable: true,
configurable: true,
});
obj.count = 5;
console.log(obj.count); // 5The computed-property gotcha
In an object literal, get x() { ... } is an accessor declaration. But if you wrote { x: function () { return 1 } } and accessed obj.x(), that is a regular method — the parser distinguishes by the get/set keyword. With computed property names, you cannot mix the two:
const key = "fullName";
// SyntaxError — you cannot do "get [key]()" with the keyword in computed names
// const o = { get [key]() { return "..." } };
// Workaround — defineProperty supports computed accessors fine
const o = {};
Object.defineProperty(o, key, {
get() { return "computed"; },
configurable: true,
});When to reach for them
A property that is derived from others:
fullName,area,total.A property that needs validation on assignment.
Lazy initialisation — compute and cache on first read, then return the cached value.
Migrating a plain field into a controlled access point without breaking callers.
When not to use them
For expensive reads — callers may not realise that
.arearuns a query against a database.When the field is fundamentally a plain data value — keep it a regular property and avoid surprise.
For setters with major side effects — call sites look harmless. Prefer an explicit
setName(value)method when the cost is non-obvious.