An object can hold methods as well as data. An ordinary function's this depends on how it is called. Calling account.describe() supplies account as the receiver; extracting that function and calling it by itself does not retain the receiver automatically. This is different from a closure retaining a surrounding variable.
Classes provide syntax for constructing objects and sharing behavior through prototypes. A prototype is another object consulted when a property is not found on the object itself. A class method is normally shared on the prototype rather than copied into each instance. Classes do not replace JavaScript's prototype model.
class StudyAccount {
#minutes = 0;
constructor(name) {
this.name = name;
}
add(minutes) {
if (!Number.isInteger(minutes) || minutes <= 0) {
throw new Error("Use positive whole minutes.");
}
this.#minutes += minutes;
}
describe() {
return `${this.name}: ${this.#minutes} minutes`;
}
}
const account = new StudyAccount("Asha");
account.add(25);
const describe = account.describe.bind(account);
console.log(describe());
console.log(Object.getPrototypeOf(account) === StudyAccount.prototype);
console.log(Object.hasOwn(account, "describe"));
The output is Asha: 25 minutes, true, and false. The #minutes field is private class state. The public add method protects the invariant that only positive whole durations are added. bind creates a function whose receiver remains the selected account. Object.hasOwn checks the object itself, so it does not mistake an inherited method for a locally stored property.
Choose a receiver deliberately
An arrow function captures the surrounding this instead of defining its own receiver. That can be useful for callbacks inside a method, but an arrow placed in an object literal does not magically become a method bound to that object. In an ES module, top-level this is undefined. Avoid fixing a receiver problem by adding arrows everywhere; identify the intended owner of the operation first.
call invokes a function with a chosen receiver and individual arguments; apply takes an array-like argument list; bind returns a new callable function. For a short event callback, a wrapper such as () => account.add(5) is often clearer than a generalized binding trick.
Encapsulation and composition
Use a class when it helps own state and enforce an invariant. A plain record plus pure functions can be simpler when there is no hidden state. Inheritance with extends shares a base contract; a subclass constructor must call super before accessing this. Avoid an inheritance tree merely to reuse a few lines. Composition means giving an object another object or function to collaborate with, such as an injected formatter or clock.
Do not modify built-in prototypes to add convenience methods. Global changes can collide with other code and make behavior hard to trace. Also distinguish TypeScript's compile-time private modifier from JavaScript # fields: a type annotation alone is not runtime access control.
Exercise: add a remaining(limit) method, define its behavior when the account already exceeds that limit, and test an invalid add call. Then implement an equivalent account using a closure and compare which values are publicly visible.
Solution guidance: make the over-budget result explicit, for example zero remaining rather than a misleading negative duration. Confirm that rejecting a bad duration preserves the previous description. Your closure version can expose only add and describe, with the accumulated value held in lexical scope.
Reference: JavaScript classes.
Previous: Arrays, Objects, Maps and Sets. Next: Modules, Packages and Project Tooling.

