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 / merge | Object.assign({}, a, b) |
Object.create(proto[, props]) | Create with prototype | const o = Object.create(null) |
Object.defineProperty(obj, key, desc) | Define 1 property | Object.defineProperty(o,'id',{value:1,writable:false}) |
Object.defineProperties(obj, descs) | Define many | Object.defineProperties(o,{a:{value:1},b:{value:2}}) |
Object.entries(obj) | [[key,value]] array | Object.entries(o).forEach(([k,v]) => ...) |
Object.fromEntries(iter) | Entries → object | Object.fromEntries(new URLSearchParams(qs)) |
Object.values(obj) | Enumerable values | Object.values(o).includes(42) |
Object.keys(obj) | Enumerable keys | for (const k of Object.keys(o)) { ... } |
Object.getOwnPropertyNames(obj) | Own string keys (incl. non-enum) | Object.getOwnPropertyNames(o) |
Object.getOwnPropertySymbols(obj) | Own symbol keys | Object.getOwnPropertySymbols(o) |
Object.getOwnPropertyDescriptor(obj, key) | Descriptor of key | Object.getOwnPropertyDescriptor(o,'x') |
Object.getPrototypeOf(obj) | Read prototype | Object.getPrototypeOf(o) === Foo.prototype |
Object.setPrototypeOf(obj, proto) | Set prototype | Object.setPrototypeOf(o, null) |
Object.is(a, b) | SameValue compare | Object.is(NaN, NaN) === true |
Object.seal(obj) | Block add/remove props | Object.seal(o) |
Object.freeze(obj) | Make immutable (shallow) | Object.freeze(config) |
Object.preventExtensions(obj) | Disallow new props | Object.preventExtensions(o) |
Object.hasOwn(obj, key) | Own key present? | Object.hasOwn(o, 'id') |
Prototype methods (on Object.prototype) |
obj.hasOwnProperty(key) | Own key check | o.hasOwnProperty('x') |
obj.propertyIsEnumerable(key) | Enumerable? | o.propertyIsEnumerable('x') |
obj.isPrototypeOf(value) | In prototype chain? | Foo.prototype.isPrototypeOf(inst) |
obj.toString() | Tag / legacy string | Object.prototype.toString.call([]) // "[object Array]" |
obj.valueOf() | Primitive hint | new Number(5).valueOf() === 5 |
obj.constructor | Ref to constructor | o.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
}