Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 413
Petr Peller Avatar asked Feb 21 '16 14:02

Petr Peller


People also ask

Why are classes not hoisted in JavaScript?

Hoisting Classes Class declarations are hoisted in JavaScript. A class declaration is uninitialized when hoisted. That means, while JavaScript can find the reference for a class we create, it cannot use the class before it is defined in the code.

Why are function expressions not hoisted?

Function expressions aren't added to the scope at all, hoisted or otherwise. This is because they are being used as a value, as opposed to function declarations, whose purpose is to create functions you can call by name. Because "hoisting" does not exist.

Which function are not hoisted?

Function expressions and class expressions are not hoisted. The expressions evaluate to a function or class (respectively), which are typically assigned to a variable. In this case the variable declaration is hoisted and the expression is its initialization.

What is hoisting in ES6?

Hoisting in Javascript is when Javascript moves variable declarations (NOT definitions) up to the top of its global or local scope. This means that var , const , and let variable declarations are interpreted as if it is at the top of its scope.


4 Answers

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.

like image 165
Bergi Avatar answered Oct 19 '22 05:10

Bergi


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.

like image 26
Oliver Sieweke Avatar answered Oct 19 '22 05:10

Oliver Sieweke


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.

like image 22
mihir hapaliya Avatar answered Oct 19 '22 07:10

mihir hapaliya


Classes are not hoisted because, for example when a class extends an expression rather than a function, error occurs:

 class Dog extends Animal {}
 var Animal = function Animal() {
 this.move = function () {
 alert(defaultMove);
 }
 }
var defaultMove = "moving";
var dog = new Dog();
dog.move();

After hoisting this will become:

var Animal, defaultMove, dog;
class Dog extends Animal {}
Animal = function Animal() {
this.move = function () {
alert(defaultMove);
}
}
defaultMove = "moving";
dog = new Dog();
dog.move();

Such at the point where class Dog extends Animal is interpreted Animal is actually undefined and we get an error. We can easily fix that by moving the Animal expression before the declaration of Dog. Pls see this great article about the topic: https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html

like image 39
Liviu Cornea Avatar answered Oct 19 '22 05:10

Liviu Cornea