3

I am learning JS and i am curious about why a function can be called before its defined & yet run fine. I understand that its due to hoisting wherein the code is scanned upfront to bring in declared variables & functions in scope.

If it were just brining the declarations of these entities in scope, why doesn't a function call also return undefined(or something similar) when being called before its definition - like variables do?

nikel
  • 3,402
  • 11
  • 45
  • 71
  • 2
    Likely for code organisation purposes. You can define a function at the bottom of the file and call it at the top. But it makes less sense to do that with a variable. Although I can't say this is the exact reasoning used when this was designed. – VLAZ Nov 03 '19 at 11:47
  • 3
    Also, just to point out - variable definitions *are* hoisted. That's why you get `undefined` instead of `ReferenceError: is not defined`. The assignment not processed until the actual `var variable = "some value"` line. – VLAZ Nov 03 '19 at 11:49
  • the 1st comment makes sense, but i would disagree with 2nd. in this statement "var x = 5;" If x is accessed before , it says undefined ; which implies the exact definition is not hoisted. since it does not give a reference error , i would say its the declaration thats hoisted. Because its same if the statement were "var x" – nikel Nov 03 '19 at 11:56
  • @nikel I think the second comment is correct. The `var` declaration is hoisted, the assignment is not. – Aluan Haddad Nov 03 '19 at 12:16
  • @VLAZ I think you're right overall, but it would make sense for a variable if that variable refers to a function – Aluan Haddad Nov 03 '19 at 12:22
  • @nikel again, as I said the *declaration* is hoisted. `var x = 5` is composed of two things: creating the `x` binding, and assignment - making that binding equal the value `5`. If you try `console.log(x)` *without* any declaration in scope, you'd get a ReferenceError. If you have `var x` *anywhere* in scope - before, after, then you do not get an error because the variable is undeclared. The *assignment* is a separate part - `x = 5` will not run at the same time as the binding is created but only when the actual line is encountered. – VLAZ Nov 03 '19 at 13:03
  • @nikel if we have to be even more technical, there are three phases to a variable's existence - declaration, initialisation, and assignment. For `var` the first two are coupled - `var x` will both declare (create the binding) and initialise (make the binding usable). Assignment can then be run as many times as you want. For `let` and `const`, the declaration and initialisation steps are separate. That's why you get the temporal dead zone - with `let y` the variable will be *declared*, so the environment will know it exists and using it before that leads to ReferenceError again. – VLAZ Nov 03 '19 at 13:07

3 Answers3

2

The difference is declaration with var is a VariableDeclaration, while declaration with function key word is a FunctionDeclaration.

The FunctionDeclaration is hoisted all together, and unlike VariableDeclaration, it has a body field, which contains the body of a function. You can spot this kind of differences using ESLint parser.

This is why:

someFunc();

function someFunc(){ console.log('someFunc'); } // hoisted as FunctionDeclaration
var someOtherFunc = () => {console.log('someOtherFunc');}; // not hoisted because the value of the variable is a function expression
var someNewFunc = function () {console.log('someNewFunc');}; // not hoisted because the value of the variable is a function expression

someOtherFunc();
someNewFunc();
Animus
  • 665
  • 12
  • 24
  • That's not quite correct. the `var` declaration is hoisted to the top of the enclosing scope, `const` does not. Both are `undefined` until the assignment is executed – Aluan Haddad Nov 03 '19 at 12:38
  • 1
    You are correct, but I'm explaining specifically hoisting of VariableDeclaration vs hoisting of FunctionDeclaration, and pointing out, that function body will not appear anywhere except FunctionDeclaration. I see the confusion here, so will change the 2nd declaration to var. – Animus Nov 03 '19 at 12:51
  • Yeah, but you increased the conceptual space by adding `const` which isn't in the question – Aluan Haddad Nov 03 '19 at 12:54
  • 2
    @AluanHaddad actually, `const` *declarations* are hoisted. Not initialisations. The reason `myVar = 5; let myVar` throws an error is because it *knows* that `let myVar` exists further down in scope. That information comes from hoisting the *declaration*. It's the initialisation phase that is not hoisted and only occurs at the line `let myVar`. This is why the error message reads `can't access lexical declaration `myVar' before initialization` - it's not *initialised*. Were it not *declared* at all, the error message would be `myVar is not defined`. – VLAZ Nov 03 '19 at 13:11
  • That is a nice clarification, I appreciate it. – Aluan Haddad Nov 03 '19 at 13:18
2

As VLAZ pointed out in the comment, variables definition are hoisted as well. Consider this example:

console.log(a) // undefined 
a = 2
console.log(a) // 2
var a
console.log(b) // ReferenceError: b is not defined

We can say that undefined is about a value of variable a. Function declarations are hoisted in the same way, but function expressions are not:

foo() // foo
bar() // TypeError: bar is not a function

function foo () { 
  console.log('foo')
}

var bar = function () {
  console.log('bar')
}
cccn
  • 929
  • 1
  • 8
  • 20
0

I have found a good explanation of why the variable is undefined at the point when it is hoisted which may look like a different behaviour if you compare it to how a function declaration is hoisted:

"JavaScript only hoists declarations, not initializations" MDN website:

However JavaScript only hoists declarations, not initializations! This means that initialization doesn't happen until the associated line of code is executed, even if the variable was originally initialized then declared, or declared and initialized in the same line.

var x = 3

x -> hoisted, becuse the JS engine come across a declarion (reserve the memory for the variable) but during the hoisting the variable won't be intislaized which means the value won't be assigned (remain undefined)

If you compare it to a function declaration,

function a() = { ... }

this doesn't have an initialization (the function isn't called), this is just a declaration.

Gabor
  • 352
  • 5
  • 14