Why are ES6 classes not hoisted?

Since ES6 classes are just a syntactical sugar over JavaScript's existing prototype-based inheritance [1] it would (IMO) make sense to hoist it's definition:

var foo = new Foo(1, 2); //this works

function Foo(x, y) {
   this.x = x;
   this.y = y;
}

But the following won't work:

var foo = new Foo(1, 2); //ReferenceError

class Foo {
   constructor(x, y) {
      this.x = x;
      this.y = y;
   }
}

Why are ES6 classes not hoisted?


Solution 1:

Why are ES6 classes not hoisted?

Actually they are hoisted (the variable binding is available in the whole scope) just like let and const are - they only are not initialised.

It would make sense to hoist its definition

No. It's never a good idea to use a class before its definition. Consider the example

var foo = new Bar(); // this appears to work
console.log(foo.x)   // but doesn't

function Bar(x) {
    this.x = x || Bar.defaultX;
}
Bar.defaultX = 0;

and compare it to

var foo = new Bar(); // ReferenceError
console.log(foo.x);

class Bar {
    constructor (x = Bar.defaultX) {
        this.x = x;
    }
}
Bar.defaultX = 0;

which throws an error as you would expect. This is a problem for static properties, prototype mixins, decorators and everything. Also it is quite important for subclassing, which broke entirely in ES5 when you used a class with its non-adjusted prototype, but now throws an error if an extended class is not yet initialised.

Solution 2:

While non-hoisted classes (in the sense that they behave like let bindings) can be considered preferable as they lead to a safer usage (see Bergi's answer), the following explanation found on the 2ality blog seems to provide a slightly more fundamental reason for this implementation:

The reason for this limitation [non-hoisting] is that classes can have an extends clause whose value is an arbitrary expression. That expression must be evaluated in the proper “location”, its evaluation can’t be hoisted.

Solution 3:

In Javascript all declarations (var, let, const, function, function*, class) are hoisted but it should be declared in same scope.

As you told "ES6 classes are just a syntactical sugar over JavaScript's existing prototype-based inheritance"

So Let's understand what it is?

Here you declared a class which is in fact "special function".Let's assume that your function Foo() and class Foo both are in global scope.

class Foo {
   constructor(x, y) {
      this.x = x;
      this.y = y;
   }
}

Following is the compiled code of your class Foo.

var Foo = (function () {
    function Foo(x, y) {
        this.x = x;
        this.y = y;
    }
    return Foo;
}());

Internally your class is converted to function with the same name inside wrapper function(iife) and that wrapper function returns your function.

Because your function's(class) scope is changed. and you are trying to create object of function in global scope which is in reality not exist.

you get the function in variable Foo once compilation comes to that. so later you have function in var you can create object of that.