OOP the Classical Way

OOP: The Classical Way

Before class, extends and #private fields (Classes), objects were built from functions. Variables inside a constructor are private: only the functions that close over them can reach them. Inheritance took two steps: Parent.call(this, ...) initializes the parent's fields, and Object.create(Parent.prototype) links the prototypes so the parent's methods are shared.

Private state via closure, constructor chaining and prototype inheritanceJavaScriptLive
function Product(name, price) {
  let discount = 0;                                  // private: only closures reach it
  this.name = name;
  this.setDiscount = (pct) => { discount = pct; };
  this.price = () => price * (1 - discount / 100);
}
Product.prototype.label = function () { return `${this.name}: ${this.price()}`; };
function Food(name, price) { Product.call(this, name, price); }  // constructor chaining
Food.prototype = Object.create(Product.prototype);   // inherit the shared methods
Food.prototype.constructor = Food;
const cheese = new Food("feta", 5);
cheese.setDiscount(20);
console.log(cheese.label(), cheese.discount, cheese instanceof Product);

The module pattern does the same without new: an IIFE or factory returns a (often frozen) object of public functions. ES modules (Export and Import) now give file-level privacy.