Hoisting of functions inside conditionals
Asked Answered
H

4

3

I understanding how hoisting in javascript occurs, functions are hoisted before variables, and only the declarations are hoisted. But When I came across hoisting inside if/else conditionals, like this one:

foo(); // "b is the output"
var a = true;

if (a) {
  function foo() { console.log("a"); }
}
else {
  function foo() { console.log("b"); }
}

Now the conditional is true, so according to the if block, a should have been the output, but due to some kind of hoisting I assume b is the output.

So how is b the output?

Howerton answered 7/2, 2016 at 6:46 Comment(3)
Something to consider: at the time you call foo() the if hasn't been executed yet so the if condition hasn't been evaluated yet. (And variable a hasn't been assigned a value yet either.)Spend
@Spend it makes no difference in the log, it still shows b , When i use immediately invoked functions it does work as expected.Howerton
Try running this function in 2018 & the foo() will output a due to some weird JS partsChigger
S
2

(Ignoring the slightly dodgy behaviour that certain old browsers may have had:)

In Javascript, function statements are scoped within the containing function (or globally if there is no containing function), they're not scoped within an if or else or loop block. So you can't declare a function conditionally in that manner (it can be done another way; see below). And if you declare more than one function with the same name in the same scope the later one will overwrite the first one.

So what happens with your code is:

  1. Both function statements are hoisted, but

  2. They both have the same name so the first is overwritten by the second.

  3. The variable, a is created but not yet assigned a value.

  4. The foo() statement is executed, logging "b"

  5. a is assigned the value true.

  6. The if is executed. The condition is true, but neither the if nor else branches actually do anything because they don't contain statements other than the function declarations that were hoisted earlier.

If you want to create functions conditionally you have to declare a variable and then assign a function expression to it. And then you can not call the function until after that assignment:

var foo;
var a = true;

if(a)
    foo = function() { console.log("a"); };
else
    foo = function() { console.log("b"); };

foo();
Spend answered 7/2, 2016 at 7:22 Comment(7)
You're welcome. Note that var doesn't have block scope either, it has function scope, but the newer variable declaration keywords let and const do have block scope.Spend
@Spend how about the result of this program?Grimy
how do you explain this snippet? ``` while(x < 2000) { if(x % 2 == 0) function aa(i) { return i + 2} else function aa(i) { return i * 2 } x = aa(x); console.log(x + ', ') }```Grimy
@Grimy - I don't, other than to make a guess (that I don't have time to research now) that browser behaviour has changed since two years ago when I wrote that answer. Both your snippet (assuming x is initialised first) and the OP's code still have the behaviour I described in my answer if I run them in IE, whereas Chrome and FF now seem to conditionally redefine the function based on the if condition. I think the first sentence of my answer was referring to old versions of IE at the time, but I can't remember now.Spend
@Grimy - P.S. Note that your snippet doesn't run at all in strict mode.Spend
If this is the case, how come if I try to run foo(); if (false) { function foo() {} }, I get Uncaught TypeError: foo is not a function? (I only tried this in the Chrome devtools console, so maybe that's not the normal result.)Verrocchio
@MMiller - The ECMA spec has moved on, and current browsers don't treat this the same way older browsers did. See this answer to a similar question for more information about current behaviour.Spend
U
2

In JavaScript, variables, function expressions and function declarations are hoisted to the top of the scope.

Function declarations defines a named function variable without requiring variable assignment.

And important to know is that the entire body of the function declaration gets hoisted up the scope.

E.g.

function outerFunction() {
    console.log(typeof functionDeclaration); // outputs "function"

    function functionDeclaration() {
        // ... function body
    }
}

This is because, because of hoisting the code runs like so:

function outerFunction() {
    function functionDeclaration() {
        // ... function body
    }

    console.log(typeof functionDeclaration); // outputs "function"
}

In your case, the last function declaration for foo is hoisted to the top of the scope overriding all the other function declarations. Therefore, it logs "b".

Variables and function expressions, however, get hoisted without their assigned values.

E.g.

function outerFunction() {
    console.log(functionExpression); // outputs "undefined"      

    var functionExpression = function () {
        // ... function body
    }
}

Runs more like so,

function outerFunction() {
    var functionExpression = undefined;

    console.log(functionExpression); // outputs "undefined"

    functionExpression = function () {
        // ... function body
    }
}
Under answered 7/2, 2016 at 6:58 Comment(3)
Thanks, so this means; 1st the foo() with a will get hoisted and and then foo() with b will get hoisted, therefore the latter will be considered and the JS engine has only one reference to a function foo so it will execute it which has console.log('b') Am I right?Howerton
function expressions are not hoisted, i think that may cause some confusion since you're saying that variable are hoisted but not initializing with the function expression valueAliber
Function expressions are NOT hoisted.Inexpedient
S
2

(Ignoring the slightly dodgy behaviour that certain old browsers may have had:)

In Javascript, function statements are scoped within the containing function (or globally if there is no containing function), they're not scoped within an if or else or loop block. So you can't declare a function conditionally in that manner (it can be done another way; see below). And if you declare more than one function with the same name in the same scope the later one will overwrite the first one.

So what happens with your code is:

  1. Both function statements are hoisted, but

  2. They both have the same name so the first is overwritten by the second.

  3. The variable, a is created but not yet assigned a value.

  4. The foo() statement is executed, logging "b"

  5. a is assigned the value true.

  6. The if is executed. The condition is true, but neither the if nor else branches actually do anything because they don't contain statements other than the function declarations that were hoisted earlier.

If you want to create functions conditionally you have to declare a variable and then assign a function expression to it. And then you can not call the function until after that assignment:

var foo;
var a = true;

if(a)
    foo = function() { console.log("a"); };
else
    foo = function() { console.log("b"); };

foo();
Spend answered 7/2, 2016 at 7:22 Comment(7)
You're welcome. Note that var doesn't have block scope either, it has function scope, but the newer variable declaration keywords let and const do have block scope.Spend
@Spend how about the result of this program?Grimy
how do you explain this snippet? ``` while(x < 2000) { if(x % 2 == 0) function aa(i) { return i + 2} else function aa(i) { return i * 2 } x = aa(x); console.log(x + ', ') }```Grimy
@Grimy - I don't, other than to make a guess (that I don't have time to research now) that browser behaviour has changed since two years ago when I wrote that answer. Both your snippet (assuming x is initialised first) and the OP's code still have the behaviour I described in my answer if I run them in IE, whereas Chrome and FF now seem to conditionally redefine the function based on the if condition. I think the first sentence of my answer was referring to old versions of IE at the time, but I can't remember now.Spend
@Grimy - P.S. Note that your snippet doesn't run at all in strict mode.Spend
If this is the case, how come if I try to run foo(); if (false) { function foo() {} }, I get Uncaught TypeError: foo is not a function? (I only tried this in the Chrome devtools console, so maybe that's not the normal result.)Verrocchio
@MMiller - The ECMA spec has moved on, and current browsers don't treat this the same way older browsers did. See this answer to a similar question for more information about current behaviour.Spend
Z
-1

The is not the correct behavior anymore in modern JS browsers! The modern browsers will compile and hoist the mentioned code like this:

// hoisting (treat foo declarations as a normal variable declaration inside if statement)
var a;
var foo;

// now the assignment
foo(); // TypeError (foo = undefined)

a = true;

if (a) {
  function foo() { console.log("a"); }
}
else {
  function foo() { console.log("b"); }
}
Zins answered 4/1, 2022 at 0:1 Comment(0)
R
-2

This is a unspecified behavior, different browser behaves diffrently.

MDN explain

In chrome & firefox, it will output foo is not a function.

And in safari, it will output b.

another doc of MDN

Retardation answered 6/3, 2020 at 16:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.