skip to content

When you write `class Child extends Parent {}` in JavaScript, which two prototype links does `extends` set up, and what does each one make possible?

level: middleimportance: should knowfreq 62%

answer

  1. a class is an object too
  2. two links, not one
  3. instances go one way, statics another
  4. the old idiom built only half of it
  5. constructor property comes for free

basics

~20 s

extends wires two links: Child.prototype inherits from Parent.prototype, which gives instances the parent's methods, and Child itself inherits from Parent, which gives the class the parent's static members and makes super work in static methods.

solid answer

~40 s

`extends` builds two parallel chains. The instance chain is `Child.prototype`'s prototype set to `Parent.prototype`, so an instance of `Child` finds inherited methods by walking up through `Child.prototype` to `Parent.prototype`. The class chain is `Child`'s own prototype set to `Parent` — classes are functions, and functions are objects, so `Child` inherits `Parent`'s static members through ordinary property lookup, and `super` inside a static method resolves along that link. `extends` also keeps `Child.prototype.constructor === Child`. The pre-class idiom `Child.prototype = Object.create(Parent.prototype)` only ever built the first chain, which is why static members had to be copied by hand and why `constructor` had to be repaired manually.

code

javascript · 10 lines
javascript
class Parent {
  static of(x) { return x; }
  greet() { return 'hi'; }
}
class Child extends Parent {}

console.log(Object.getPrototypeOf(Child.prototype) === Parent.prototype); // true
console.log(Object.getPrototypeOf(Child) === Parent);                     // true
console.log(new Child().greet(), Child.of(1));                            // hi 1
console.log(Child.prototype.constructor === Child);                       // true

go deeper

for a junior

Be able to say that a subclass gets both the parent's instance methods and its static members, and that you do not have to wire anything up by hand.

for a middle

Name the two links precisely and show them with Object.getPrototypeOf on both the class and its .prototype, explaining which lookup each one serves.

for a senior

Demonstrate diagnostic instinct: when a subclass is missing a member, identify which of the two chains is broken and what code severed it, such as a post-declaration reassignment of .prototype.

for a principal

Discuss what the live class link buys a framework or library API — parent additions propagate to every subclass — versus the copy-based emulation it replaced, and where inherited statics create surprising coupling.

## Classes are objects, so there are two chains A class declaration produces a constructor function. Functions are objects, so a class participates in prototype lookup twice over: once as the thing instances delegate to (via its `.prototype` property) and once as an object in its own right (via its own `[[Prototype]]`). `extends` sets both. ```js class Parent { static of(x) { return x; } greet() { return 'hi'; } } class Child extends Parent {} Object.getPrototypeOf(Child.prototype) === Parent.prototype; // true — instance chain Object.getPrototypeOf(Child) === Parent; // true — class chain ``` ### The instance chain `Child.prototype` is a fresh object whose `[[Prototype]]` is `Parent.prototype`. Instance method lookup therefore walks `instance -> Child.prototype -> Parent.prototype -> Object.prototype -> null`. Methods written in the `Child` body sit on `Child.prototype` and shadow same-named parent methods further up the chain, which is what makes overriding work. `extends` also gives the new `Child.prototype` a `constructor` property pointing back at `Child`, non-enumerable like everything else the class body installs. ### The class chain `Child`'s own `[[Prototype]]` is `Parent`, rather than the `Function.prototype` a normal function gets. This is a plain object link, so anything reachable on `Parent` as a property — including anything the class body installed on the constructor object — is reachable on `Child` by ordinary lookup. It is also the link that `super(...)` follows to find the parent constructor, and the link `super.x` follows inside a static method. A standalone class with no `extends` has `Function.prototype` as its `[[Prototype]]`, which is why `class C {}` gives you `C.call`, `C.bind` and nothing else. ## What `extends <expr>` accepts The operand is an arbitrary expression, evaluated once at class definition time. It must yield either `null` or a constructor — a function with a `[[Construct]]` behaviour and a `prototype` property that is an object or `null`. ```js const mixin = (Base) => class extends Base { ping() { return 'pong'; } }; class App extends mixin(Parent) {} ``` Because the expression runs at definition time, class factories like the one above are a language feature rather than a trick — the anonymous class expression is a perfectly ordinary value. Two shapes fail loudly: - `class C extends 42 {}` throws a `TypeError`: the value is not a constructor. - `class C extends someArrowFn {}` throws a `TypeError`: arrow functions have no `[[Construct]]`. `extends null` is legal but strange. It sets `Child.prototype`'s `[[Prototype]]` to `null`, so instances inherit nothing at all — no `toString`, no `hasOwnProperty` — and it still marks the constructor as derived. The implicit constructor calls `super()`, and there is no parent constructor to call, so `new C()` throws. Only a hand-written constructor that returns an explicit object can instantiate such a class, which is why the construct is a curiosity rather than a technique. ## Why the pre-class idiom was incomplete The ES5 pattern only ever built the instance chain: ```js function Child() { Parent.call(this); } Child.prototype = Object.create(Parent.prototype); Child.prototype.constructor = Child; // had to be restored by hand ``` The constructor object `Child` still inherited from `Function.prototype`, so nothing on `Parent` itself was visible on `Child`. Libraries papered over that by copying properties from the parent constructor onto the child constructor — a copy, so later additions to the parent were never picked up, and accessors were flattened into data properties in the process. `extends` replaces the copy with a live link. ## How this shows up in practice When you see a subclass mysteriously missing something the parent has, ask which chain the missing member lives on and check the corresponding link. `Object.getPrototypeOf` on both the class and its `.prototype` answers the question in two lines. The most common way to break the wiring is to reassign `Child.prototype` after the class is declared — which severs the instance chain while leaving the class chain intact, producing a subclass whose statics inherit correctly but whose instances have lost the parent's methods.

  • What is Object.getPrototypeOf(class C {}) for a class written without extends?
    `Function.prototype`. A class is a function object, and with no `extends` clause it gets the ordinary function prototype, so it inherits `call`, `apply` and `bind` and nothing more. The `extends` clause is what replaces that default with the parent class, which is exactly why static inheritance appears only in subclasses.
  • Why does extends accept an arbitrary expression rather than only an identifier?
    Because the clause is evaluated once at class definition time like any other expression. That allows `class X extends mixin(Base) {}`, where a function returns an anonymous class expression, and it allows conditional or computed base classes. The only requirement is that the result be `null` or a value with a `[[Construct]]` behaviour; anything else throws a `TypeError`.
  • What does super.method() resolve to inside a static method?
    It walks the class chain rather than the instance chain: the engine takes the prototype of the class the static method was defined in and looks the name up there. So inside a static method of `Child`, `super.method()` finds `Parent`'s corresponding member and invokes it with `this` still bound to the class the call went through.

saying these in an interview costs you the question

  • Says extends only links the prototypes of instances
  • Thinks static members are copied onto the subclass
  • Believes Child.prototype.constructor must be fixed by hand
  • Claims classes cannot extend an arbitrary expression
  • Assumes extends null yields an ordinary instantiable class

context