DMarlon Studio

Lesson 5 · 5 of 20

Getters, setters and private fields · JavaScript

Protect object state in JavaScript with # private fields, validate values with setters and compute properties with getters.

JavaScript · Getters, setters and private fields in DMarlon Studio: the example's code and its output in the terminal

Fields that start with `#` are private: only code inside the class can read or change them. #price and #changes are declared at the top with their initial values, and from outside they simply do not exist: mouse["#price"] is undefined and Object.keys(mouse) only shows name. Writing mouse.#price outside the class would be a syntax error.

A setter (set price(value)) looks like a normal property but runs code on assignment. That is why mouse.price = 18 goes through validation while mouse.price = -3 throws a RangeError, which the program catches and prints. Even the constructor uses this.price = price, so the starting price is validated and counted as a change too: hence 2 changes.

A getter (get price()) is read without parentheses: mouse.price. It also gives you computed properties: priceWithVat is not stored anywhere; it is worked out from #price every time. Since it has no setter, the assignment mouse.priceWithVat = 1 does nothing (in strict mode it would throw a TypeError).

Together, private fields, getters and setters give you encapsulation: the object decides what is visible, what can change and under which rules, so nobody can leave it in an invalid state, such as a product with a negative price.

Code · main.js

class Product {  #price = 0; // private: only code inside the class can touch it  #changes = 0;  constructor(name, price) {    this.name = name;    this.price = price; // goes through the setter below  }  get price() {    return this.#price;  }  set price(value) {    if (typeof value !== "number" || value < 0) {      throw new RangeError(`Invalid price for ${this.name}: ${value}`);    }    this.#price = value;    this.#changes++;  }  get priceWithVat() {    return +(this.#price * 1.21).toFixed(2);  }  get changes() {    return this.#changes;  }}const mouse = new Product("Mouse", 20);mouse.price = 18;console.log(mouse.name, mouse.price, "->", mouse.priceWithVat, "with VAT");try { mouse.price = -3; } catch (err) { console.log(`${err.name}: ${err.message}`); }console.log("Price is still", mouse.price, "after", mouse.changes, "changes");mouse.priceWithVat = 1; // a getter without a setter ignores assignmentsconsole.log("With VAT:", mouse.priceWithVat, "| keys:", Object.keys(mouse).join(", "));console.log("From outside:", mouse["#price"]);

Output · node main.js

Mouse 18 -> 21.78 with VAT
RangeError: Invalid price for Mouse: -3
Price is still 18 after 2 changes
With VAT: 21.78 | keys: name
From outside: undefined

Key points

  • A #private field is only reachable from inside the class.
  • A setter validates or transforms the value on every assignment.
  • A getter reads like a property and can compute its value.
  • A getter with no setter is a read-only property.

Exercise

Add a discount setter that accepts a percentage between 0 and 50, stores it in a private #discount field and makes priceWithVat apply it; try one valid discount and one of 80.