JS Story

JS Story: Objects and Classes

prototype model, Object API, ES2015+ class syntax

3.0 The Prototype Model

Every JavaScript object has an internal link to another object called its prototype. When you access a property that doesn't exist on the object itself, the engine walks up the prototype chain until it finds the property or reaches null.
const animal = {
  speak() { return `${this.name} makes a noise.`; }
};

const dog = Object.create(animal);   // dog's prototype is animal
dog.name = 'Rex';

console.log(dog.speak());            // Rex makes a noise.
console.log(Object.getPrototypeOf(dog) === animal);   // true
ES2015 class syntax wraps the same mechanism. A class definition creates a constructor function and attaches methods to its prototype object. The extends keyword sets up the prototype chain between two constructor functions.
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a noise.`; }
}

class Dog extends Animal {
  speak() { return `${super.speak()} Woof!`; }
}

const d = new Dog('Rex');
console.log(d.speak());                      // Rex makes a noise. Woof!
console.log(d instanceof Dog);               // true
console.log(d instanceof Animal);            // true - chain includes Animal
The same chain structure applies to browser built-ins. A DOM element's prototype chain runs through five levels before reaching Object.prototype:
const btn = document.querySelector('button');

// walk the prototype chain manually
let proto = Object.getPrototypeOf(btn);
while (proto) {
  console.log(proto.constructor.name);
  proto = Object.getPrototypeOf(proto);
}
// HTMLButtonElement
// HTMLElement
// Element
// Node
// EventTarget
// Object

// instanceof confirms every level
console.log(btn instanceof HTMLButtonElement);  // true
console.log(btn instanceof HTMLElement);        // true
console.log(btn instanceof Element);            // true
console.log(btn instanceof Node);              // true
console.log(btn instanceof EventTarget);        // true
The equivalent class hierarchy in JavaScript - this is how the browser engine defines these built-in types internally:
class EventTarget {
  addEventListener(type, fn, opts) { /* ... */ }
  removeEventListener(type, fn)    { /* ... */ }
  dispatchEvent(event)             { /* ... */ }
}

class Node extends EventTarget {
  // parentNode, childNodes, textContent, cloneNode, ...
}

class Element extends Node {
  // querySelector, setAttribute, classList, innerHTML, ...
}

class HTMLElement extends Element {
  // style, dataset, offsetWidth, focus, click, ...
}

// Each HTML tag has its own subclass adding tag-specific members:
class HTMLButtonElement extends HTMLElement {
  // type, disabled, form, value, ...
}

class HTMLInputElement extends HTMLElement {
  // type, value, checked, validity, ...
}

// Custom elements follow exactly the same pattern:
class MyWidget extends HTMLElement {
  connectedCallback()    { /* runs when added to DOM */ }
  disconnectedCallback() { /* runs when removed from DOM */ }
}

3.1 JavaScript Object API

Every plain JavaScript value derives from Object. Static methods operate on objects as a whole - copying, sealing, freezing, enumerating keys. Prototype methods are available on every object instance.
Table 1. - Object Static and Prototype Methods
Signature Brief description Example
Static methods (on Object)
Object.assign(target, ...sources)Shallow copy / mergeObject.assign({}, a, b)
Object.create(proto[, props])Create with prototypeconst o = Object.create(null)
Object.defineProperty(obj, key, desc)Define 1 propertyObject.defineProperty(o,'id',{value:1,writable:false})
Object.defineProperties(obj, descs)Define manyObject.defineProperties(o,{a:{value:1},b:{value:2}})
Object.entries(obj)[[key,value]] arrayObject.entries(o).forEach(([k,v]) => ...)
Object.fromEntries(iter)Entries → objectObject.fromEntries(new URLSearchParams(qs))
Object.values(obj)Enumerable valuesObject.values(o).includes(42)
Object.keys(obj)Enumerable keysfor (const k of Object.keys(o)) { ... }
Object.getOwnPropertyNames(obj)Own string keys (incl. non-enum)Object.getOwnPropertyNames(o)
Object.getOwnPropertySymbols(obj)Own symbol keysObject.getOwnPropertySymbols(o)
Object.getOwnPropertyDescriptor(obj, key)Descriptor of keyObject.getOwnPropertyDescriptor(o,'x')
Object.getPrototypeOf(obj)Read prototypeObject.getPrototypeOf(o) === Foo.prototype
Object.setPrototypeOf(obj, proto)Set prototypeObject.setPrototypeOf(o, null)
Object.is(a, b)SameValue compareObject.is(NaN, NaN) === true
Object.seal(obj)Block add/remove propsObject.seal(o)
Object.freeze(obj)Make immutable (shallow)Object.freeze(config)
Object.preventExtensions(obj)Disallow new propsObject.preventExtensions(o)
Object.hasOwn(obj, key)Own key present?Object.hasOwn(o, 'id')
Prototype methods (on Object.prototype)
obj.hasOwnProperty(key)Own key checko.hasOwnProperty('x')
obj.propertyIsEnumerable(key)Enumerable?o.propertyIsEnumerable('x')
obj.isPrototypeOf(value)In prototype chain?Foo.prototype.isPrototypeOf(inst)
obj.toString()Tag / legacy stringObject.prototype.toString.call([]) // "[object Array]"
obj.valueOf()Primitive hintnew Number(5).valueOf() === 5
obj.constructorRef to constructoro.constructor === Object

3.2 JavaScript Classes (ES2015+)

Class definitions create a constructor function and attach methods to its prototype. Private fields (#field), static members, and class blocks are modern additions that allow patterns common in other languages.
Class syntax examples
// ── Fields, methods, getter/setter, static ────────────────────────────
class Point {
  x = 0;  y = 0;                          // public instance fields

  constructor(x, y) { this.x = x; this.y = y; }

  length() { return Math.hypot(this.x, this.y); }

  get r()  { return this.length(); }
  set r(v) {
    const a = Math.atan2(this.y, this.x);
    this.x = v * Math.cos(a); this.y = v * Math.sin(a);
  }

  static origin() { return new Point(0, 0); }
}

// ── Private fields and methods ────────────────────────────────────────
class Counter {
  #value = 0;
  static #MAX = 1_000_000;

  get value() { return this.#value; }

  #clamp(n) { return Math.min(n, Counter.#MAX); }

  increment() { this.#value = this.#clamp(this.#value + 1); }

  static from(n) { const c = new Counter(); c.#value = c.#clamp(n); return c; }
}

// ── Inheritance ───────────────────────────────────────────────────────
class Animal {
  constructor(name) { this.name = name; }
  speak() { return `${this.name} makes a noise.`; }
}

class Dog extends Animal {
  constructor(name) { super(name); }       // must call super() before `this`
  speak() { return `${super.speak()} Woof!`; }
}

// ── Static block (runs once when the class is evaluated) ──────────────
class Config {
  static base = '/api';
  static #token;

  static {
    this.#token = crypto.randomUUID?.() ?? 'dev-token';
  }

  static authHeader() { return { Authorization: `Bearer ${this.#token}` }; }
}

// ── Abstract-like base (throws if instantiated directly) ──────────────
class Base {
  constructor() {
    if (new.target === Base) throw new TypeError('Abstract - use a subclass');
  }
}

// ── Bound arrow field keeps `this` without .bind() ────────────────────
class Button {
  count = 0;
  handleClick = () => { this.count++; };   // safely passed as a callback
}