JavaScript Basics: Model Data with Classes
A class groups data with methods that work on it. Start with a public field, then use private fields when changes should go through your own methods.
These examples use built-in features and were tested on Linux with Node.js v24.19.0. No packages are needed. Assertions check the results before the program finishes.
Prepare a scratch directory
You need Bash, a text editor, Node.js on your PATH, and the mktemp, rm, and rmdir utilities. Keep using the same terminal. These commands stop the shell if setup fails. Save each named source file as UTF-8; .mjs selects ES modules.
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 11. Create objects with a class
Save this as reading-task.mjs:
import assert from 'node:assert/strict';
class ReadingTask {
completed = false;
constructor(title) {
this.title = title;
}
finish() {
this.completed = true;
}
}
const firstTask = new ReadingTask('Read chapter one');
const secondTask = new ReadingTask('Review the examples');
firstTask.finish();
assert.equal(firstTask.completed, true);
assert.equal(secondTask.completed, false);
assert.equal(firstTask.finish, secondTask.finish);
console.log(`${firstTask.title}: ${firstTask.completed}`);
console.log(`${secondTask.title}: ${secondTask.completed}`);Run it:
node reading-task.mjsOutput:
Read chapter one: true
Review the examples: falsenew creates an instance and runs its constructor. Each task has its own fields, while the finish method is shared through the class prototype. In firstTask.finish(), this refers to firstTask. Both fields are public, so other code can change them directly. See the class reference.
2. Keep a field private
Save this as private-score.mjs:
import assert from 'node:assert/strict';
class ScoreCard {
#points = 0;
awardPoint() {
this.#points += 1;
}
readPoints() {
return this.#points;
}
}
const firstCard = new ScoreCard();
const secondCard = new ScoreCard();
firstCard.awardPoint();
firstCard.awardPoint();
firstCard.points = 99;
assert.equal(firstCard.readPoints(), 2);
assert.equal(secondCard.readPoints(), 0);
assert.equal(firstCard.points, 99);
console.log(`Private points: ${firstCard.readPoints()}`);
console.log(`Other card: ${secondCard.readPoints()}`);
console.log(`Separate public property: ${firstCard.points}`);Run it:
node private-score.mjsOutput:
Private points: 2
Other card: 0
Separate public property: 99The #points declaration creates a private field. It is distinct from the public points property. Writing firstCard.#points outside the class would be a syntax error in this file. A private field controls access through JavaScript syntax; it is not encryption for secrets.
3. Validate changes before storing them
Save this as stock-item.mjs:
import assert from 'node:assert/strict';
class StockItem {
#quantity = 0;
constructor(quantity) {
this.setQuantity(quantity);
}
get quantity() {
return this.#quantity;
}
setQuantity(quantity) {
if (!Number.isSafeInteger(quantity) || quantity < 0) {
throw new RangeError('Quantity must be a nonnegative safe integer');
}
this.#quantity = quantity;
}
}
const pencils = new StockItem(12);
pencils.setQuantity(9);
assert.equal(pencils.quantity, 9);
assert.throws(() => pencils.setQuantity(-1), RangeError);
assert.throws(() => pencils.setQuantity(1.5), RangeError);
assert.throws(() => new StockItem('12'), RangeError);
assert.equal(pencils.quantity, 9);
console.log(`Quantity after valid change: ${pencils.quantity}`);
console.log('Invalid quantities rejected; quantity is still 9');Run it:
node stock-item.mjsOutput:
Quantity after valid change: 9
Invalid quantities rejected; quantity is still 9The getter makes pencils.quantity readable without a method call. Changes use setQuantity(), which checks the value before assigning the private field. The constructor uses the same check. A rejected update leaves the previous value intact. Private fields do not validate data by themselves; the method supplies that rule.
Clean up
In the same terminal, remove only the example files, return to your original directory, and remove the empty scratch directory:
rm -- reading-task.mjs private-score.mjs stock-item.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirAcknowledgements
This post was created with assistance from AI.