JavaScript Prototypes & Inheritance Cheat Sheet
Covers the prototype chain, constructor functions, ES6 class syntax, and Object.create for building inheritance in JavaScript.
The Prototype Chain
Property lookups walk up the chain until found.
const animal = { eats: true, walk() { return "walking"; },};const rabbit = Object.create(animal); // rabbit's prototype is animalrabbit.jumps = true;rabbit.eats; // true -- found on animal via the prototype chainrabbit.walk(); // "walking" -- inherited methodObject.getPrototypeOf(rabbit) === animal; // true// Every object chain ends at Object.prototype, then nullObject.getPrototypeOf(Object.prototype); // null
Constructor Functions
The pre-ES6 way to build objects with shared methods.
function Animal(name) { this.name = name;}// Methods go on the prototype so all instances share one copyAnimal.prototype.speak = function () { return `${this.name} makes a sound`;};const dog = new Animal("Rex");dog.speak(); // "Rex makes a sound"dog instanceof Animal; // truedog.__proto__ === Animal.prototype; // true (legacy accessor)
ES6 Classes (Syntactic Sugar)
Classes still use prototypes under the hood.
class Animal { constructor(name) { this.name = name; } speak() { return `${this.name} makes a sound`; }}class Dog extends Animal { constructor(name, breed) { super(name); // Calls Animal's constructor this.breed = breed; } speak() { return `${super.speak()} (woof!)`; // Extend, not just override }}const rex = new Dog("Rex", "Lab");rex.speak(); // "Rex makes a sound (woof!)"rex instanceof Animal; // true -- classes still use prototypes under the hood
Object.create & Prototypal Patterns
Build objects with an explicit prototype.
const proto = { greet() { return `Hi, I'm ${this.name}`; },};const person = Object.create(proto, { name: { value: "Alice", enumerable: true },});person.greet(); // "Hi, I'm Alice"// Check own vs inherited propertiesperson.hasOwnProperty("name"); // trueperson.hasOwnProperty("greet"); // false (inherited)Object.create(null); // Object with no prototype at all -- no inherited methods
Key Concepts
Core vocabulary for prototype-based inheritance.
- [[Prototype]]- Internal slot every object has, accessed via Object.getPrototypeOf() or __proto__
- prototype- Property on functions/classes; becomes the [[Prototype]] of instances created with new
- instanceof- Checks whether a constructor's prototype appears anywhere in an object's chain
- class- Syntactic sugar over prototype-based inheritance, not a separate object model
- extends / super- Sets up the prototype chain between classes and calls the parent constructor/method
- Object.setPrototypeOf()- Changes an existing object's prototype (slow -- prefer Object.create at creation time)
Mixins for Multiple Behavior Composition
JS has single inheritance via `extends`; mixins compose behavior across unrelated hierarchies.
const Serializable = (Base) => class extends Base { toJSON() { return { ...this }; }};const Comparable = (Base) => class extends Base { equals(other) { return JSON.stringify(this) === JSON.stringify(other); }};class Point { constructor(x, y) { this.x = x; this.y = y; }}class ComparablePoint extends Comparable(Serializable(Point)) {}const p = new ComparablePoint(1, 2);p.toJSON(); // { x: 1, y: 2 }p.equals(new ComparablePoint(1, 2)); // true -- mixins stack via the prototype chain
Property Descriptors & Accessors
Fine-grained control over enumerability, writability, and getter/setter behavior.
const obj = {};Object.defineProperty(obj, "id", { value: 42, writable: false, // Assignment silently fails (throws in strict mode) enumerable: false, // Hidden from for...in and Object.keys configurable: false, // Cannot be redefined or deleted});Object.defineProperty(obj, "label", { get() { return `#${this.id}`; }, set(v) { console.warn("label is derived, ignoring:", v); }, enumerable: true,});obj.label; // "#42"Object.getOwnPropertyDescriptor(obj, "id");// { value: 42, writable: false, enumerable: false, configurable: false }// Class syntax equivalent for getters/setters on the prototype:class Circle { #radius; constructor(r) { this.#radius = r; } get area() { return Math.PI * this.#radius ** 2; } // Defined on Circle.prototype, not per-instance}
Private Fields & Static Members
True encapsulation (not just convention) plus class-level shared state.
class Counter { #count = 0; // Truly private -- not on the prototype, not accessible externally static #instances = 0; // Private static, shared across the whole class constructor() { Counter.#instances += 1; } increment() { this.#count += 1; return this.#count; } static get instanceCount() { return Counter.#instances; } #logInternal() { // Private method console.log(this.#count); }}const c = new Counter();c.increment();// c.#count; // SyntaxError -- private fields are inaccessible outside the class bodyCounter.instanceCount; // 1
Reflect & Proxy for Prototype-Level Interception
Intercept fundamental operations (get, set, has) instead of overriding methods one by one.
function createValidated(target, schema) { return new Proxy(target, { set(obj, prop, value, receiver) { if (schema[prop] && typeof value !== schema[prop]) { throw new TypeError(`${String(prop)} must be a ${schema[prop]}`); } return Reflect.set(obj, prop, value, receiver); // Delegates to default behavior }, get(obj, prop, receiver) { console.log(`accessed ${String(prop)}`); return Reflect.get(obj, prop, receiver); }, });}const user = createValidated({}, { age: "number" });user.age = 30; // OK// user.age = "old"; // throws TypeError// Reflect mirrors Proxy traps 1:1, making Reflect.* the correct default forwarding callReflect.has(user, "age"); // true, same as `"age" in user`Reflect.ownKeys(user); // Own property keys, including symbols
Advanced Vocabulary
Terms that come up once you move past everyday class usage.
- Object.freeze vs Object.seal- freeze prevents adding, removing, and modifying properties; seal prevents adding/removing but still allows modifying existing writable properties
- Species pattern (Symbol.species)- Lets built-in subclasses (e.g. of Array) control which constructor is used by methods like .map() that return a new instance
- new.target- Inside a constructor, refers to the constructor actually invoked with `new`, letting a base class detect and branch on subclassing
- Symbol.hasInstance- Customizes what `instanceof` checks for a class, overriding the default prototype-chain walk
- Non-standard __proto__ vs Object.getPrototypeOf- __proto__ is a legacy accessor kept for web compatibility; getPrototypeOf/setPrototypeOf are the spec-sanctioned API and work on objects without it
- Class fields evaluation order- Public/private instance fields are initialized top-to-bottom at the start of the constructor, after super() returns in a derived class
Defining methods inside a constructor function (this.speak = function(){}) creates a new function per instance; put shared behavior on the prototype (or use class methods, which do this automatically) to save memory.