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.
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);feta: 4 undefined true
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.