2

We know that function declaration are hoisted and you can call them from anywhere in your script. That is not the case for function expressions.

For example:

 test();

 const test = () => {
    console.log(1+3);
 }

 When we call test() it will always return undefined.

But this does not happen when we call the same function inside expressjs middleware.

router.get('/', (req, res, next) => {
   test(); // it will return always the result 4
})

const test = () => {
   console.log(1+3);
}

Can someone explain to me why this happens?

Astrit Spanca
  • 643
  • 5
  • 13
  • 1
    The module code runs when the module is included, so at the time the `router.get` callback runs, `const test` is already in memory. This works in the browser, too: https://jsfiddle.net/khrismuc/n724ezvL/ –  Aug 05 '19 at 20:48
  • 1
    That `test` call in the second snippet isn’t in the same scope as `const test` at all. Also, `const` and `let` declarations aren’t hoisted anyway. `test(); const test = () => {};` results in a [TDZ error](https://stackoverflow.com/q/33198849/4642212). – Sebastian Simon Aug 05 '19 at 20:59
  • [`let` and `const` are hoisted as well](https://stackoverflow.com/q/31219420/1048572), just without their value initialsation. And the `get` callback isn't called immediately, but after the variable was initialised. – Bergi Aug 05 '19 at 21:56
  • @SebastianSimon it has nothing to do with viarable types in this case – Astrit Spanca Aug 05 '19 at 22:03
  • @AstritSpanca Yes, it has nothing to do with variable types. I didn’t say anything about variable types. – Sebastian Simon Aug 05 '19 at 22:41

1 Answers1

5

Your first snippet, when run exactly as you show it, generates this error:

ReferenceError: test is not defined

Your second code snippet works because the module is initialized and const test is defined. Then some time later, the route callback gets called and test is now defined and has a value in the module scope.

For let and const, it is an error to reference them in running code before they are initialized. That's why the first code snippet generates a ReferenceError.

But, once initialized, they are available anywhere within the scope in which they are defined. Some people call this hoisting to the top of the scope. Because this works differently than var hoisting, I just think of it as once the const or let symbol is defined, it's available anywhere within its scope, even in code that might get called later, but appears earlier in the file. This is consistent with Javascript's run-time lookup for variables. When executing the route handler in your second code snippet, the symbol test is looked up dynamically within its scope. Since const test has already run and been initialized by the time the route handler gets called, it finds an already initialized test variable and test() works just fine.

In your first code snippet, the dynamic lookup for test fails because const test has not yet run when you try to call test() and that creates the ReferenceError.


You can see a demonstration of that a little more simply in this code which is pretty much the same as your second snippet:

function a() {
    test();
}

const test = () => {
    console.log(1+3);
}

a();

Here, we define function a and then define const test as a function. Then, we call a() which in turn calls test(). Since the sequence of events was:

  1. Define function a
  2. Define const test as a function
  3. Call a()
  4. Which then calls test()

And, all symbols are accessible from the module scope, then you can see that test is defined before it is called so the code works.

jfriend00
  • 683,504
  • 96
  • 985
  • 979