ESC

Type to search the knowledge base.

Classes in JavaScript

JS classes as sugar over prototypes — constructors, extends, super, fields, and what still differs from classical OOP.

beginner3 min read
  • javascript
  • classes
  • prototype
  • oop

class syntax is not a new object model. It is cleaner syntax for constructor functions + prototypes. Under the hood you still have prototypes, [[Prototype]] links, and method sharing on .prototype. That framing keeps you honest in interviews and debugging.

Shape of a class

class User {
  constructor(name) {
    this.name = name;
    this.createdAt = Date.now();
  }

  greet() {
    return `Hi, ${this.name}`;
  }

  static isUser(x) {
    return x instanceof User;
  }
}

const u = new User('Ada');
u.greet(); // "Hi, Ada"
User.isUser(u); // true
typeof User; // "function"
u.greet === User.prototype.greet; // true — shared method

Calling User() without new throws. Class bodies are always strict mode.

extends and super

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

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // must call before using this
    this.breed = breed;
  }
  speak() {
    return `${super.speak()} — woof`;
  }
}

const d = new Dog('Rex', 'mutt');
d.speak(); // "Rex makes a noise — woof"
d instanceof Dog; // true
d instanceof Animal; // true

super.method() resolves on the parent prototype. super() in the constructor invokes the parent constructor.

Fields (public and private)

class Counter {
  #value = 0; // private
  label = 'count'; // public field (per instance)

  inc() {
    this.#value += 1;
    return this.#value;
  }

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

const c = new Counter();
c.inc(); // 1
// c.#value // SyntaxError outside class

Public fields land on the instance; methods stay on the prototype (unless you use arrow class fields, which create per-instance functions).

class Widget {
  // per-instance; lexical this — useful for handlers
  onClick = () => {
    console.log(this);
  };

  // shared on prototype
  render() {}
}

What classes do not give you

  • Multiple inheritance
  • True method overloading
  • Private members that erase at runtime for “security” (they’re language-enforced, not encryption)
  • Automatic binding of methods when detached:
const { inc } = new Counter();
// inc() // loses this if it used this without an arrow field

Prototype equivalence (mental model)

// roughly equivalent idea
function User(name) {
  this.name = name;
}
User.prototype.greet = function () {
  return `Hi, ${this.name}`;
};

extends sets up Subclass.prototype → Parent.prototype and Subclass → Parent for static inheritance.

Interview answer

“Classes are sugar over constructor functions and prototypes. Methods live on Class.prototype; fields usually on the instance. extends chains prototypes; super calls the parent. I still think about this-binding when passing methods as callbacks, and I use private fields for encapsulation instead of underscore conventions.”

Static blocks and inheritance of statics

class Config {
  static defaults = { theme: 'light' };
  static {
    // runs once at class evaluation
    this.frozen = Object.freeze({ ...this.defaults });
  }
}

class AdminConfig extends Config {}
AdminConfig.defaults; // inherited static via prototype chain on the constructor

extends sets up two chains: instance prototypes and constructor statics. super.staticMethod() works inside static methods. Prefer composition (inject collaborators) when inheritance trees get deeper than one or two levels — JS classes don’t fix deep hierarchy design problems.

class Service {
  constructor(deps) {
    this.deps = deps;
  }
}
// clearer than SuperService → MegaService → Service soup

Further reading

Related guides