WebDev Story

WebDev Story: JavaScript Objects & Classes

Object API, prototype model, ES2015+ class syntax

5.0 JavaScript Object API

Every plain JavaScript value derives from Object. Its static methods operate on objects as a whole (copying, sealing, freezing, enumerating keys), while 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.getOwnPropertyDescriptors(obj)All descriptorsObject.getOwnPropertyDescriptors(o)
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.isSealed(obj)Sealed?Object.isSealed(o)
Object.freeze(obj)Make immutable (shallow)Object.freeze(config)
Object.isFrozen(obj)Frozen?Object.isFrozen(config)
Object.preventExtensions(obj)Disallow new propsObject.preventExtensions(o)
Object.isExtensible(obj)Extensible?Object.isExtensible(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.toLocaleString()Locale stringdate.toLocaleString('en-US')
obj.valueOf()Primitive hintnew Number(5).valueOf() === 5
Prototype properties / accessors
obj.__proto__ (getter/setter)Legacy proto accessobj.__proto__ = null (prefer getPrototypeOf)
obj.constructorRef to constructoro.constructor === Object

5.1 JavaScript Classes (ES2015+)

JavaScript classes provide syntactic sugar over the prototype model. Under the hood, a class definition creates a constructor function and attaches methods to its prototype. Private fields (#field), static members, and class blocks are modern additions.
Class Syntax Examples
// ── Basic class with fields, methods, getter/setter, static ──────────
class Point {
  x = 0;                    // public instance field (per instance)
  y = 0;

  constructor(x, y) {       // runs on `new Point(...)`
    this.x = x; this.y = y;
  }

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

  get r() { return this.length(); }                 // getter
  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); }       // static method
}

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

  get value() { return this.#value; }
  #clamp(n) { return Math.min(n, Counter.#MAX); }   // private method

  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!`; }       // override + call base
}

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

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

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

// ── Computed method names ─────────────────────────────────────────────
class Greeter {
  ['say' + 'Hi']() { return 'hi'; }
  static ['v' + 1] = 2;            // computed static field
}

// ── Abstract-like base ────────────────────────────────────────────────
class Base {
  constructor() {
    if (new.target === Base) throw new TypeError('Abstract');
  }
}

// ── Arrow method keeps `this` bound without .bind() ──────────────────
class Button {
  count = 0;
  handleClick = (e) => { this.count++; };   // bound arrow as field
}