JAVASCRIPT / PROTOTYPES, CLASSES, AND INHERITANCE
Class syntax and constructor methods
Write classes with a constructor and methods, and predict exactly which properties land on each instance and which are shared on the prototype.
What you will learn
- Declare a class with a constructor and instance methods and create objects with new
- Predict which properties are own to an instance and which sit on Class.prototype
- Explain why a class constructor throws a TypeError when called without new
- Set up per-instance state with class fields, parameters, and default values
Understanding Class syntax and constructor methods
A `class` declaration creates two linked things at once: a constructor function bound to the class name, and an object at `Name.prototype` that holds the methods. Everything between the braces is evaluated one time, when the class itself is defined, so a method written there exists as a single function object shared by every instance. The mental model that pays off is a wiring diagram with two destinations: methods go to the prototype, and whatever the constructor assigns to `this` goes to one individual object. The same syntax works as an expression, `const Timer = class { ... }`, because a class is just a value you can store or pass around.
The `constructor` method is the only part of the body that runs when you write `new`. By the time it starts, `this` is already bound to the fresh instance, so `this.label = label` creates an own, enumerable property on that one object, and every `new` call produces a separate set of those properties. A class may have at most one `constructor`; leave it out and the class behaves as if it contained an empty one, so `new Name()` still yields a working object that simply has no own properties yet. Since the constructor is an ordinary function body, parameter defaults, destructuring, and argument validation all behave as they would in any other function.
Class syntax also changes a few rules quietly. The entire body is strict-mode code, so a method that loses its receiver sees `this === undefined` instead of the global object, which converts a silent bug into a `TypeError` at the point of failure. The class binding is not hoisted the way a function declaration is; it stays in a temporal dead zone until the declaration is evaluated. And the constructor is marked as requiring `new`, so both `Name()` and `ids.map(Name)` throw rather than running the body with the wrong `this`.
Because methods are installed on the prototype as non-enumerable properties, `Object.keys(instance)` and `for...in` show only the data the constructor put there. That is usually what you want when logging or serialising an object, and it is also the fastest way to check that you put shared behaviour and per-instance state in the right places.
class Timer {
constructor(label, seconds) {
this.label = label;
this.remaining = seconds;
}
tick() {
if (this.remaining > 0) this.remaining -= 1;
return this.remaining;
}
describe() {
return this.label + ': ' + this.remaining + 's left';
}
}
const t = new Timer('steep tea', 3);
t.tick();
t.tick();
console.log(t.describe());
console.log('own properties:', Object.keys(t).join(', '));
console.log('on the prototype:', Object.getOwnPropertyNames(Timer.prototype).join(', '));
console.log('one shared function:', t.tick === Timer.prototype.tick);
try {
Timer('no new', 1);
} catch (err) {
console.log(err.name + ': ' + err.message);
}A class body is evaluated once to place shared methods on the prototype, while the constructor runs on every `new` to give each instance its own state.
Worked examples
Fields versus constructor assignment
Shows that class fields and constructor assignments both produce own properties, and that they run in a fixed order.
class Counter {
count = 0;
step;
constructor(step = 1) {
this.step = step;
}
increment() {
this.count += this.step;
return this.count;
}
}
const a = new Counter(5);
const b = new Counter();
console.log(a.increment(), a.increment());
console.log(b.increment());
console.log(Object.keys(a).join(', '));
console.log(Object.getOwnPropertyNames(Counter.prototype).join(', '));Example explained
Line 1`count = 0` is an instance field: it is evaluated per instance, before the constructor body, so it becomes an own property.
Line 2`step;` with no initialiser still creates the property as `undefined`; the constructor then overwrites it with the argument or the default `1`.
Line 3`a` and `b` mutate separate `count` properties, which is why `b.increment()` returns 1 after `a` has reached 10.
Line 4`increment` appears only in the prototype listing, so both instances call the same function object.
What a constructor returns
Demonstrates that returning an object from a constructor replaces the new instance, while returning a primitive is ignored.
class Wrapper {
constructor(value) {
this.value = value;
return { value };
}
}
class Plain {
constructor(value) {
this.value = value;
return value.length;
}
}
const w = new Wrapper('hi');
const p = new Plain('hi');
console.log(w instanceof Wrapper, w.value);
console.log(p instanceof Plain, p.value);Example explained
Line 1`new` normally evaluates to the object bound to `this`, which is why a constructor almost never needs a `return` at all.
Line 2`return { value }` hands back a different object, so `w` is a plain object and `w instanceof Wrapper` is false.
Line 3`return value.length` produces a number, and primitive return values from a constructor are discarded, so `p` is the real instance.
Line 4An accidental object return therefore breaks `instanceof` and every method reached through the prototype.
Strict mode inside the class body
Shows why a class method pulled off its instance throws instead of silently reading from the global object.
class Greeter {
constructor(name) {
this.name = name;
}
greet() {
return 'hello, ' + this.name;
}
}
const g = new Greeter('ada');
console.log(g.greet());
const detached = g.greet;
try {
detached();
} catch (err) {
console.log(err.name + ': ' + err.message);
}
const bound = g.greet.bind(g);
console.log(bound());Example explained
Line 1`g.greet()` works because the call expression supplies `g` as the receiver.
Line 2`detached()` supplies no receiver, and class bodies are always strict-mode code, so `this` is `undefined` rather than `globalThis`.
Line 3In sloppy-mode code the same call would have quietly read `globalThis.name`; strictness is what turns this into a visible error.
Line 4`bind(g)`, or wrapping the call in an arrow function, is what you need whenever a method goes to `map`, `setTimeout`, or an event listener.
Important notes
Class declarations are not hoisted like function declarations: the name is in a temporal dead zone, so `new Queue()` written above `class Queue {}` throws `ReferenceError: Cannot access 'Queue' before initialization`.
The wording of these error messages is engine-specific (V8 wording is shown here); only the error type is guaranteed by the language spec.
Common mistakes
Separating members with commas, as in `class A { constructor() {}, run() {} }` — a class body is not an object literal, so this is a SyntaxError and nothing in the file runs.
Using the class where a plain function is expected, such as `ids.map(User)` — each call throws `TypeError: Class constructor User cannot be invoked without 'new'`; write `ids.map(id => new User(id))` instead.
Defining methods inside the constructor with `this.run = function () { ... }` — it works, but you create a fresh function per instance and it becomes an own enumerable property that shows up in `Object.keys()` and `for...in`.
Try it yourself
Change, predict, then run
In a browser console, write a `Rectangle` class whose constructor takes `width` and `height` and which has an `area()` method. Create two rectangles of different sizes, then confirm that `Object.keys(r1)` lists only the two dimensions while `r1.area === r2.area` is `true`.
Open the JavaScript workspaceCheck your understanding
A class `Car` has a constructor that assigns `this.speed` and a method `accelerate()`. You create 1000 cars. How many distinct function objects exist for `accelerate`, and why?
- 1000 — every `new` call re-evaluates the class body and builds a fresh method
- 1000 — methods declared in a class body are copied onto each instance as own properties
- 1 — the class body is evaluated once and `accelerate` lives on `Car.prototype`, shared by all instances
- 1 — the engine deduplicates functions with identical source text at runtime
Show answer
The class body is evaluated a single time, when the class is defined, and its methods are installed on `Car.prototype`; only the constructor body re-runs on each `new`, and that is what creates the per-instance `speed` property. The '1000' answers confuse the constructor with the body — they would be correct only if you had written `this.accelerate = function () { ... }` inside the constructor, which really does allocate one function per car.