69

JavaScript's class syntax, added in ES6, apparently makes it legal to extend null:

class foo extends null {}

new foo();

Some Googling reveals that it was suggested on ES Discuss that such declarations be made an error; however, other commenters argued for them to be left legal on the basis that

someone might want to create a class that has a {__proto__: null} prototype

and that side of the argument ultimately prevailed.

I can't make much sense of this hypothetical use case. For one thing, while the declaration of such a class is legal, it seems that instantiating a class declared in this way isn't. Trying to instantiate the class foo from above in Node.js or Chrome gives me the wonderfully daft error

TypeError: function is not a function

while doing the same in Firefox gives me

TypeError: function () {
} is not a constructor

It doesn't help to define a constructor on the class, as shown in MDN's current example of the feature; if I try to instantiate this class:

class bar extends null {
  constructor() {}
}

new bar();

then Chrome/Node tell me:

ReferenceError: this is not defined

and Firefox tells me:

ReferenceError: |this| used uninitialized in bar class constructor

What is all this madness? Why are these null-extending classes not instantiable? And given that they're not instantiable, why was the possibility of creating them deliberately left in the spec, and why did some MDN author think that it was noteworthy enough to document? What possible use case is there for this feature?

Mark Amery
  • 143,130
  • 81
  • 406
  • 459
  • 1
    Have the `constructor` return `Object.create(null)` as a substitute. This apparently lets you avoid the automatic `super` call. https://jsfiddle.net/w7y2mbcd/ But then I guess it's not actually a member of that class. –  Dec 16 '16 at 17:33
  • 2
    Maybe have it return `Object.create(bar.prototype)`. https://jsfiddle.net/w7y2mbcd/1/ –  Dec 16 '16 at 17:39
  • @squint nice - your second comment provides a way to actually instantiate a null-extending class, resulting in an object that doesn't have any of the methods from `Object.prototype`, like `.toString()` or `.isPrototypeOf()`. That's further than I got, although I'm still not quite sure why you'd want such an object. – Mark Amery Dec 16 '16 at 17:43
  • 1
    I think sometimes people just want a clean object. This was more important before we had `Map`, so they'd use `Object.create(null)` to use as a generic map that can hold arbitrary properties that are not known in advance so that you can guarantee that any property lookup came from the interaction with the object instead of a default inheritance. Seems less important now, but then again there are probably some uses cases here and there. Don't know what they are though. –  Dec 16 '16 at 17:48
  • IMO, it would be better to not have to jump through hoops; the `super` call should just be avoided if there's no parent constructor. But then I haven't really thought it through either. –  Dec 16 '16 at 17:51
  • @squint you've referenced an implicit `super` call a couple of times in this comment thread, but not explained it in detail (perhaps assuming more detailed knowledge of ES6 classes from your audience than I personally have). Is that implicit call the cause of the errors trying to instantiate the `bar` from my question? Under what circumstances does this implicit call happen, and why? – Mark Amery Dec 16 '16 at 17:53
  • I have limited familiarity with this syntax, but my understanding is that when you use the `class` syntax, the "parent" constructor is automatically invoke on the object or that if you try to reference `this`, you must first manually call `super()`... but I may have some details confused, so you'll be better off looking at other resources for a better explanation. But yes, I think that's probably the cause of the errors, though I could be very wrong about that. –  Dec 16 '16 at 18:02
  • 1
    FWIW, the [spec](http://www.ecma-international.org/ecma-262/7.0/#sec-class-definitions) says that `extends` can be followed by any *LeftHandSideExpression*, which basically means anything that could be put left of a `=`. That includes things like `foo()` and `null`. Now, neither `null = 42;` nor `foo() = 42` make sense, but they are valid and will throw a runtime error. OTOH, `class Foo extends bar() {}` makes sense. *edit:* Uh, `foo() = 42` and `null = 42` seem to be *early errors*, which are almost like syntax errors :-/ – Felix Kling Dec 16 '16 at 18:15
  • I have the same theory as Felix, it is allowed not because it is useful, but because it keeps the language uniform and "simple". By simple I mean, the docs read "any LeftHandSideExpression" instead of "any LeftHandSideExpression except null or etc." – givanse Dec 16 '16 at 23:47

3 Answers3

30

EDIT (2021): TC39, which specifies JavaScript still hasn't resolved exactly how this is supposed to work. That needs to happen before browsers can consistently implement it. You can follow the latest efforts here.


Original answer:

Instantiating such classes is meant to work; Chrome and Firefox just have bugs. Here's Chrome's, here's Firefox's. It works fine in Safari (at least on master).

There used to be a bug in the spec which made them impossible to instantiate, but it's been fixed for a while. (There's still a related one, but that's not what you're seeing.)

The use case is roughly the same as that of Object.create(null). Sometimes you want something which doesn't inherit from Object.prototype.

Bakkot
  • 1,175
  • 7
  • 13
  • So let's say I want to hook up every objects prototype BUT one I create with `extend null` or `Object.create(null)`? – Stefan Rein Dec 19 '16 at 14:04
  • 2
    It looks like the spec regarding `extends null` has [changed](https://github.com/tc39/ecma262/commit/c57ef95c45a371f9c9485bb1c3881dbdc04524a2), and [Chrome isn't fixing this bug because of it](https://bugs.chromium.org/p/v8/issues/detail?id=5115#c11). – Decade Moon Feb 19 '17 at 10:35
  • Apparently this is still not fixed. What’s taking them so long? – user3840170 Jun 13 '23 at 10:46
14

To answer the second part:

I can't make much sense of this hypothetical use case.

That way, your object won't have Object.prototype in its prototype chain.

class Hash extends null {}
var o = {};
var hash = new Hash;
o["foo"] = 5;
hash["foo"] = 5;
// both are used as hash maps (or use `Map`).
hash["toString"]; // undefined
o["toString"]; // function

As we know, undefined in fact is not a function. In this case we can create objects without fearing a call on a field that shouldn't be there.

This is a common pattern through Object.create(null) and is common in a lot of code bases including big ones like Node.js .

(Note new class Foo extends null {} creates an object with an empty object as its prototype whereas Object.create(null) creates an object with null as its prototype)

Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
  • `new class extends null {}` and `Object.create(null)` are not synonyms. The former (well, if we pretend it were actually working, anyway) has a prototype of its own (it just doesn’t derive from `Object.prototype`), the latter doesn’t. – user3840170 Aug 15 '23 at 06:32
  • @user3840170 yes you're right this answer predates "class reform" spec changes, I'll update it. – Benjamin Gruenbaum Aug 16 '23 at 19:43
  • Wait actually where do I say in this answer they're the same? – Benjamin Gruenbaum Aug 16 '23 at 19:43
  • In the last paragraph, ‘This is a common pattern through `Object.create(null)`’ implies they are equivalent. – user3840170 Aug 28 '23 at 20:21
2

It is in fact possible to construct a class that extends null, like follows:

class bar extends null {
    constructor() {
        super() //required
    }
}

//super is not a function
//change the super class (for static methods only)
Object.setPrototypeOf(bar, class {});

let instance = new bar();
//instances still have null prototype
console.log(instance.toString); //undefined
user3840170
  • 26,597
  • 4
  • 30
  • 62
BlobKat
  • 88
  • 1
  • 8