OOP#

JavaScript’s object model is prototype-based. Every object has a prototype chain that is walked on property reads. ES2015 class is sugar over that mechanism, with ergonomics (extends, super, static, #private, get / set) that make the prototype machinery invisible most days.

For the function surface methods sit on, see Functions. For Object, Map, Set, and other container objects, see Data Structures.

Plain objects#

The shortest path to an object is the object literal. Keys are strings or symbols; values are anything.

const point = {x: 1, y: 2};
point.x;                    // 1
point["x"];                 // same
const k = "x"; point[k];    // same

const o = {
  [`key_${i}`]: "computed",        // computed key
  greet(name) { return `hi, ${name}`; },   // method shorthand
};

Spread and Object.assign shallow-copy.

const merged = {...a, ...b};
const copy   = Object.assign({}, original);
const frozen = Object.freeze({...defaults, ...overrides});

Classes#

class declares a constructor function plus a prototype with the methods attached.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  distance(other) {
    const dx = this.x - other.x;
    const dy = this.y - other.y;
    return Math.hypot(dx, dy);
  }
}

const p = new Point(0, 0);
const q = new Point(3, 4);
p.distance(q);              // 5

Method shorthand (distance(other) { ... }) puts the function on Point.prototype; that prototype is shared across instances.

Inheritance#

extends chains prototypes; super(...) calls the parent constructor; super.method(...) calls the parent method.

class Animal {
  constructor(name) { this.name = name; }
  speak() { console.log(`${this.name} makes a sound`); }
}

class Dog extends Animal {
  speak() {
    super.speak();
    console.log(`${this.name} barks`);
  }
}

new Dog("rex").speak();

Multiple inheritance is not native; mixins (functions that extend a class) cover the common cases.

const Loggable = Base => class extends Base {
  log(msg) { console.log(`[${this.name}] ${msg}`); }
};

class Server extends Loggable(Animal) {}

Visibility#

#name declares a private field (or method). Private names are truly inaccessible from outside the class, enforced by the runtime.

class Cache {
  #data = new Map();             // private field
  #hits = 0;

  get(k) {
    const v = this.#data.get(k);
    if (v !== undefined) this.#hits++;
    return v;
  }

  stats() { return {hits: this.#hits, size: this.#data.size}; }
}

const c = new Cache();
c.#data;                         // SyntaxError; private outside class

static puts a method or field on the class itself, not the prototype.

class Server {
  static DEFAULT_PORT = 8080;
  static create() { return new Server(); }
}
Server.create();

Accessors#

get and set give read / write semantics that look like properties but run code.

class Temp {
  constructor(c) { this._c = c; }
  get fahrenheit() { return this._c * 9/5 + 32; }
  set fahrenheit(f) { this._c = (f - 32) * 5/9; }
}

const t = new Temp(100);
t.fahrenheit;               // 212
t.fahrenheit = 32;          // sets _c via the setter

this binding#

this inside a method is the receiver of the call.

const p = new Point(1, 2);
p.distance(q);              // this = p inside distance

const fn = p.distance;
fn(q);                      // TypeError; this is undefined

Bind a method if it must be passed around standalone.

const bound = p.distance.bind(p);
bound(q);                   // works

Arrow methods on class fields capture this lexically (useful for event handlers).

class Component {
  constructor() {
    this.label = "go";
  }
  onClick = () => console.log(this.label);   // safe to pass around
}

Prototypes#

A class is the constructor function plus a prototype object. Object.getPrototypeOf(obj) walks the chain; obj.__proto__ is the legacy accessor for the same thing.

Object.getPrototypeOf(p) === Point.prototype;    // true
Object.getPrototypeOf(Point.prototype) === Object.prototype;   // true

Point.prototype.translate = function (dx, dy) {
  this.x += dx; this.y += dy;
};
p.translate(1, 1);                  // method monkey-patched

The operator monkey-patches sparingly; it confuses tooling and breaks under module re-evaluation.

instanceof#

x instanceof C walks the prototype chain looking for C.prototype. Works for built-ins too.

[] instanceof Array;            // true
"x" instanceof String;          // false (primitive)
new Date() instanceof Date;     // true

Across realm boundaries (iframe, worker), prototypes differ; Array.isArray(x) is the cross-realm-safe array check.

References#