Course outline · 0% complete

0/27 lessons0%

Course overview →

class syntax: what it really does

lesson 3-2 · ~12 min · 7/27

class is a cleaner spelling of prototypes

A class does two things you already understand.

  1. The constructor runs when you call new ClassName(...), with this set to a brand-new object.
  2. Every method you write in the class body is placed on one shared prototype object, ClassName.prototype.

Instances get methods through the prototype chain from lesson 3-1, not as copies. Nothing new is happening in the object model.

Here is what new does, step by step.

StepWhat happens
1Create a new empty object
2Link its prototype to ClassName.prototype
3Run the constructor with this bound to that object
4Return the object, unless the constructor returns its own

class is the spelling you will actually read and write at work, including error subclasses, component classes, and ORM models.

Interviews probe whether you know it is the same prototype machinery underneath, and the reason is that the abstraction leaks.

this still follows the call-site rules from unit 2, so a method pulled off an instance still breaks. Methods still live in exactly one shared place, so patching a prototype still affects every instance.

Two instances, one method

The method exists once no matter how many objects use it.

class User {
  constructor(name) {
    this.name = name;
  }
  greet() {
    return "Hi, " + this.name;
  }
}

const a = new User("Ada");
const b = new User("Linus");

console.log(a.greet());
console.log(b.greet());
console.log(a.greet === b.greet);
console.log(Object.getPrototypeOf(a) === User.prototype);

Output

Hi, Ada
Hi, Linus
true
true

greet exists exactly once, on the shared prototype, and the === check proves both objects reach the same function.

The last line is the connection to the previous lesson. Object.getPrototypeOf(a) is User.prototype, so this is the Object.create chain with different syntax.

name is different for each instance because the constructor assigned it with this.name = name, creating an own property on each new object.

So a.hasOwnProperty("name") is true and a.hasOwnProperty("greet") is false, which is the data-versus-behavior split from the shape example.

Forgetting new is a real failure mode worth knowing. User("Ada") throws a TypeError, since class constructors refuse plain calls, while an old-style constructor function would silently do the wrong thing.

what you writeclass Dog {constructor(n) {this.name = n;}speak() { ... }}iswhat it meansfunction Dog(n) {this.name = n;}Dog.prototype .speak = ...One shared speak on the prototype either way, never one copy per dog.
class syntax desugars to a constructor function plus methods assigned onto its prototype object.

Why interviewers care

Before class arrived in 2015, the same machinery was written with constructor functions.

function User(name) {
  this.name = name;
}
User.prototype.greet = function () {
  return "Hi, " + this.name;
};

You will still see this in older codebases and interview questions, and it behaves identically. new User("Ada") runs the same four steps, and greet still lives on User.prototype.

The key line to say in an interview is short. "class is mostly syntax over prototypes. Methods live on the prototype and instances reach them through the prototype chain."

The word "mostly" is honest rather than hedging, because a few behaviors are genuinely new. Class bodies are strict mode automatically, class declarations are not hoisted the way function declarations are, and constructors throw when called without new.

Also remember the point from lesson 2-1. A method pulled off an instance still loses this like any other function, so const g = a.greet; g() throws whether the method came from a class or a hand-built prototype.

One copy, living on User.prototype and shared through the prototype chain.

Class methods are not copied onto instances. Each instance's prototype link points at the same User.prototype, where the single greet lives.

That sharing is the memory win of prototypes, and it is the reason the language resolves methods by walking a chain rather than by copying a table into every object.

The properties assigned in the constructor are the opposite case. A thousand instances hold a thousand name strings, because those are own properties by design.

There is one syntax that breaks the sharing, and it is worth watching for. A class field written as an arrow, such as greet = () => "Hi, " + this.name, is assigned per instance in the constructor, so a thousand instances mean a thousand functions.

People reach for that form on purpose in React class components, because a per-instance arrow captures this and survives being passed as a callback. The trade is memory for convenience, and knowing that it is a trade is the point.

Two class tools you will meet immediately

A getter, written get fahrenheit() { ... }, is a method you read like a property, with no parentheses.

Use it for values computed from other fields. Since it recomputes on every read, it can never go stale the way a stored copy could.

That freshness is the real argument for getters. A fahrenheit field assigned once in the constructor would be wrong the moment celsius changed, and a getter cannot be.

The cost is that the work is invisible at the call site. A getter that does something expensive looks exactly like a property read, so keep them cheap.

A static method lives on the class itself rather than on instances, as in Temperature.fromFahrenheit(60).

Statics hold alternative constructors and helpers that belong to no single instance. You have already used one, since Object.create is a static method on Object.

Inside a static method this is the class, not an instance, which is what makes return new this(...) work in a subclass-friendly factory.

A getter and a static factory

Two different call shapes in one class.

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  get fahrenheit() {
    return this.celsius * 9 / 5 + 32;
  }
  static fromFahrenheit(f) {
    return new Temperature((f - 32) * 5 / 9);
  }
}

const boiling = new Temperature(100);
console.log(boiling.fahrenheit);
console.log(Temperature.fromFahrenheit(32).celsius);

Output

212
0

boiling.fahrenheit has no parentheses, so the getter runs on read.

Writing boiling.fahrenheit() would throw, since the getter already returned the number 212 and numbers are not callable.

fromFahrenheit is called on the class itself and builds an instance for you, which is why .celsius works on its result.

boiling.fromFahrenheit is undefined, and that is the defining property of a static. It is on Temperature, not on Temperature.prototype.

The getter is on the prototype like any method, so it is shared. Only its access syntax differs.

Assigning boiling.fahrenheit = 100 fails silently in sloppy mode and throws in strict mode, because a getter without a matching setter is read-only. Adding set fahrenheit(v) { this.celsius = (v - 32) * 5 / 9 } makes it writable.

Rectangle

A constructor storing two values and two methods reading them.

class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }
  area() {
    return this.width * this.height;
  }
  isSquare() {
    return this.width === this.height;
  }
}

const r = new Rectangle(3, 7);
console.log(r.area());
console.log(r.isSquare());
console.log(new Rectangle(5, 5).isSquare());

Output

21
false
true

The constructor stores its two parameters on this, and both methods read them back through the same keyword.

isSquare uses === rather than ==, which is the habit to keep. Strict equality never coerces, and lesson 10-2 shows what goes wrong when it does.

The last line calls a method directly on a fresh instance without ever naming it, which is legal and common. new Rectangle(5, 5) is an expression, so the dot applies to its result.

Both methods sit on Rectangle.prototype, so r.area === new Rectangle(1, 1).area is true.

area could reasonably be a getter here, since it computes from fields and takes no arguments. Choosing between r.area() and r.area is an API decision rather than a correctness one.