-1

I'm talking about:

class MyAction {
}
class MyActionEdit extends MyActionNew { // <-- error pointing here
}
class MyActionNew extends MyAction {
}

I'm getting the following error: Uncaught ReferenceError: Cannot access 'MyActionNew' before initialization.

I'm not creating an instance, before all js is loaded. So I don't understand, why hoisting is not working here. The problem is: I cannot change the order of the class definitions (they are loaded from various files and combined to one big script - 3 combined files in this example).

I've already read this and many other questions, but none did answer my question.

This here is working for example:

class MyAction {
}
let test: MyActionNew = new MyActionNew(); // <- Hoisting works here
class MyActionEdit extends MyAction {
}
class MyActionNew extends MyAction {
}

How can I deal with this? I don't know if this helps, but I'm using typescript to generate the javascript files. May be there is something?

Related: Typescript class: will class be hoisted when I use two classes?

I've read all question with the tag "hoisting" here on stackoverflow, but did not find an answer.

SuperNova
  • 2,792
  • 2
  • 25
  • 34
  • up there you extend `MyActionNew` and down you extend `MyAction` witch of them is now correct – bill.gates Sep 11 '20 at 06:48
  • 1
    Javascript is executed in two steps: 1) parsing/compilation, 2) runtime. Hoisting works because certain things like variable names are created during the parsing step, and hence already exist at runtime. But what you want happens all during the parse step, and during that step the order must be logical; there is no hoisting there. – deceze Sep 11 '20 at 06:48
  • in short - `why hoisting is not working here` - because class definitions are not hoisted – Jaromanda X Sep 11 '20 at 06:50
  • Your second example [does not work on the Typescript Playground](https://www.typescriptlang.org/play?#code/MYGwhgzhAECyCeBBYAXAlgewHbQN4CgBffEAUxWhVIhQC44lVMsA5Ugd2gF5osOHk6bG3YAKAJQBuaAHoZ0ADwBaaAAkMaGmiwBzaOwwAnANYwAFqUOl8oSDASDmAUQAmaCqQAeVLC-uMhHAJiWygBJmF+Lx8-cMC8IiA). – Titulum Sep 11 '20 at 06:50
  • @Thomas There's no circular extension here. It's just `MyAction` → `MyActionNew` → `MyActionEdit`. Just the order of declaration is messed up. – deceze Sep 11 '20 at 07:03
  • In the related link is an example with the Hero class. There are many answer here in stackoverflow, that are telling, that classes are hoisted and that this behaviour changed between ES5 and ES6. Take a look at this answer: https://stackoverflow.com/questions/35537619/why-are-es6-classes-not-hoisted/35537963#35537963 – SuperNova Sep 11 '20 at 07:42

2 Answers2

2

JavaScript classes are not hoisted. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

Griffin
  • 196
  • 2
  • 8
  • I'd already read this. But this seems only be true for the class keyword. As I'm using typescript, I can output the class to any other format. But I don't understand, if there is an declaration, where hoisting will work or not. I've spend hours googeling and reading stack overflow, before I posted my question. – SuperNova Sep 11 '20 at 07:44
2

In the related link is an example with the Hero class. There are many answer here in stackoverflow, that are telling, that classes are hoisted and that this behaviour changed between ES5 and ES6. Take a look at this answer: Why are ES6 classes not hoisted?

Yes but no. It is correct that the class name is hoisted. However, that it rather irrelevant to your situation.

Javascript is executed in two steps:

  1. Parsing/compilation
  2. Runtime

In the parsing step, the Javascript engine takes your code apart, reads it, tokenises it, and generally converts it into an executable form. In this step, it lays out general structures it's going to use during runtime, among them the names of things and what scope they belong to. So, the name MyActionEdit will be created in the correct scope. That is what hoisting is. Because in the second step, the runtime, when the code actually runs, that name will already exist, even if it comes later in the scope:

console.log(foo);

var foo = 'bar';

This executes like:

  1. Parse var foo and create a scope with that name reserved.
  2. Runtime: execute console.log(foo), then execute foo = 'bar'.

That's why this doesn't raise an error and why it logs undefined. This is what "hoisting" is.

Now, classes are also defined in their entirety during parse time. The parse step creates the classes. If that parse step succeeds, then the classes are already "hoisted" and available at runtime.* But that is rather irrelevant, since the order of declarations during parse time is wrong, and Javascript cannot extend a class which hasn't been declared yet. It would have to ignore that first class declaration, skip to the next one, and then repeat the parsing process to declare the previously skipped declaration. That can lead to infinite loops and is generally extremely inefficient. Or if it could "hoist" classes first, in what order should they be hoisted?! How should the engine know to hoist the class declaration in the order they need to be in for the declarations to make logical sense?

It's up to you to declare your classes in the correct order.

* Except they're not accessible before their declaration because this hoisting behaviour has been deemed confusing and is explicitly being suppressed by the TDZ.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • Okay, that sadly makes sense to me. So I've to change my file collector to manage a correct order for concatenating files. Thank you for clarification. I already accepted your answer. – SuperNova Sep 11 '20 at 09:25