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?
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.
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.
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.
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.
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 extend
ed class is not yet initialised.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With