Need to understand Javascript function hoisting example
Asked Answered
U

5

23

I read the concept of Javascript Hoisting.Its pretty confusing but I saw some examples and got the idea what hoisting actually does.

So basically "Hoisting is JavaScript's default behavior of moving all declarations to the top of the current scope (to the top of the current script or the current function)."

But I am not able to understand the below implementation :

var is_android = true;
if (is_android) {
    function foo() {
        alert('I am Android');
    }
} else {
    function foo() {
        alert('I am NOT Android');
    }
}
foo();

The output shows "I am NOT Android" in alert box.

I want to know why foo() is called from else block even if is_android value is true .

Any help will be appreciated.

Uniliteral answered 17/4, 2014 at 5:49 Comment(4)
I also stuck with the same behavior of JavaScriptAi
Forget "hoisting". Just remember that all function and variable declarations are processed before any code is executed. Oh, Felix said that…Letterperfect
Your code is actually an error in some browsers, specifically because of it almost never resulting in the behaviour you actually wanted.Canova
@NiettheDarkAbsol : This the working example and I have seen in many docs referring this example.Uniliteral
A
21

tl;dr: Don't use something that looks like a function declaration inside a block, especially not a conditional.


Fact is, most browsers interpret this piece of code the wrong way. They treat the function definitions as function declarations, even though function declarations are not allowed inside blocks, since a function declaration is not a statement, it's a source element.

This how it works in general:

Before the code is even executed, the interpreter looks for all variable and function declarations, not matter where they are, and creates a binding for them in the current/new environment. Then it starts actually executing the code.

So, assuming that the function definitions are interpreted as declarations, since there are two declarations, the last one wins. Your code basically becomes:

function foo() {
    alert('I am Android');
}
function foo() {
    alert('I am NOT Android');
}
var is_android;

is_android = true;
if (is_android) {

} else {

}
foo();

Other engines would interpret it differently, but still incorrectly (IMO, see below):

In the following script, the zero function is never defined and cannot be invoked, because 'if (0)' evaluates its condition to false:

if (0) {
   function zero() {
      document.writeln("This is zero.");
   }
}

Note: Some JavaScript engines, not including SpiderMonkey, incorrectly treat any function expression with a name as a function definition. This would lead to zero being defined, even with the always-false if condition. A safer way to define functions conditionally is to define the function anonymously and assign it to a variable:

if (0) {
   var zero = function() {
      document.writeln("This is zero.");
   }
}

But in this case, if the function definition truly was interpreted as function expression (similarly to (function zero() { ... })), then the name of the function would not be accessible in the containing scope, and the function would just be lost.

Adnah answered 17/4, 2014 at 5:52 Comment(13)
I was also under the same impression, but this code doesn't work the same way for variables.Enrollee
I wonder if the order is always maintained as seems to be the case here.Meadowlark
@thefourtheye: In which browser does the code contradict? What output do you get?Adnah
@FelixKling I used Node.js and the link has the output of the program at the bottom of the code. Can you please explain that?Enrollee
@FelixKling Also, contradicts is not the right word I believe. I updated the comment.Enrollee
@thefourtheye: Oh, so you weren't confused about the function output, but the variable value? The assignment (initialization) of the variable still tales place inside the if...else statement.Adnah
@FelixKling So, all var will be done at runtime and function declarations will be taken care at compile time. Is that right?Enrollee
@FelixKling—Kanagax has a pretty good rundown on issues with named function expressions.Letterperfect
@thefourtheye— all declarations are done before any code is run. Function bodies are only evaluated when they are called, so initially the function name is essentially a variable of the scope in which it's declared and its body is assigned, waiting to be executed…Letterperfect
@thefourtheye: The variable declaration itself is also hoisted, just the assignment takes place where it is in the code. Overall it's even more complicated than that, I updated my answer.Adnah
@FelixKling : Thanks a lot for your explaination.Uniliteral
@Sid: I hope I didn't make too complicated now. The way the spec is defined and how browsers work are two very different animals sometimes.Adnah
@FelixKling : Not at all. You saved my day :-) Learning JS is a bit challenging.Uniliteral
J
5

In Javascript there are subtle differences between the two similar-looking

 function square(x) { return x*x; }

and

 var square = function(x) { return x*x; }

The biggest difference is that in the first case the assignment of the function object to the square name is done when entering the scope, not when reaching the line. This allows for calling functions that are defined later in the source code... for example:

console.log(cube(12));
function cube(x) { return x*x*x; }

is a valid script that will work, even if the call happens "before" the function definition (and, by the way, allowing this kind of code is IMO the reason for the rule being in the language).

In the second case instead the assignment is just a regular statement and it's executed when (and if) the flow of control passes through it.

If you want that snippet to work as you expect then simply change the code from

function <name> (...) { ... }

to

var <name> = function (...) { ... }

PS: you can repeat the name also in the second form, but it's not normally done and anonymous functions are used instead.

Jackelynjackeroo answered 17/4, 2014 at 6:2 Comment(0)
N
3

http://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html

this is the best article i have read about hositing.

Declarations, Names, and Hoisting

In JavaScript, a name enters a scope in one of four basic ways:

Language-defined: All scopes are, by default, given the names this and arguments. Formal parameters: Functions can have named formal parameters, which are scoped to the body of that function. Function declarations: These are of the form function foo() {}. Variable declarations: These take the form var foo;. Function declarations and variable declarations are always moved (“hoisted”) invisibly to the top of their containing scope by the JavaScript interpreter. Function parameters and language-defined names are, obviously, already there. This means that code like this:

function foo() {
    bar();
    var x = 1;
}

is actually interpreted like this:

function foo() {
    var x;
    bar();
    x = 1;
}

It turns out that it doesn’t matter whether the line that contains the declaration would ever be executed. The following two functions are equivalent:

function foo() {
    if (false) {
        var x = 1;
    }
    return;
    var y = 1;
}
function foo() {
    var x, y;
    if (false) {
        x = 1;
    }
    return;
    y = 1;
}

Notice that the assignment portion of the declarations were not hoisted. Only the name is hoisted. This is not the case with function declarations, where the entire function body will be hoisted as well. But remember that there are two normal ways to declare functions. Consider the following JavaScript:

function test() {
    foo(); // TypeError "foo is not a function"
    bar(); // "this will run!"
    var foo = function () { // function expression assigned to local variable 'foo'
        alert("this won't run!");
    }
    function bar() { // function declaration, given the name 'bar'
        alert("this will run!");
    }
}
test();

In this case, only the function declaration has its body hoisted to the top. The name ‘foo’ is hoisted, but the body is left behind, to be assigned during execution.

That covers the basics of hoisting, which is not as complex or confusing as it seems. Of course, this being JavaScript, there is a little more complexity in certain special cases.

Ngocnguyen answered 17/4, 2014 at 5:51 Comment(2)
StackOverflow is not meant to be just a collection of links to soon-to-become-404 external pages.Jackelynjackeroo
@sid glad it helped you , not a correct answer though.Ngocnguyen
N
3

You may already understand the way to declare the functions to avoid hoisting, but in case not: you can write it as:

var is_android = true;
if (is_android) {
    var foo = function() {
        alert(5);
    };
} else {
    var foo = function() {
        alert(7);
    };
}
foo();

and foo() will not be evaluated until the javascript interpreter has evaluated the conditional statement.

Nard answered 17/4, 2014 at 5:59 Comment(2)
Can you explain the logic behind this?Uniliteral
@Sid One could say (it is an abuse of language) that such function declaration (the one in the question) are scopeless: they are parsed before execution even if the else is not actually reached during execution. That is not the case when you write it as LexJacobs did.Help
B
2

you are mixing concepts.

Start with this code:

function foo() {
    alert('1');
}
function foo() {
    alert('2');
}
foo();

This won't give error. Instead just alerts 2. Because second definition of foo() overrides the first one.

Try now following code:

if (false) {
    function foo() { alert('false');}
}
foo();

This will just alert false. Even though foo is defined inside a block that is not executed (an if false) function declarations are always processed by JavaScript.

With these two examples in mind is easy to understand what happens in your code: You define twice a function foo, and the second definition overrides the first.

What you were trying in your code is something very close to "conditional compilation" and this behaviour can be simulated in JavaScript declaring functions as variables:

if (true) {
    var foo = function() {alert ('true');}
}
else {
    var foo = function() {alert ('false');}
}
foo(); 

This code just alerts true. Note that now foo is defined as a variable (containing a function, but a variable).

Greetings

Bozovich answered 17/4, 2014 at 6:0 Comment(1)
"This will just alert false"—that is correct in most cases, however there was a clause in ECMA-262 ed 3 exploited by some browsers that allowed the above to be treated as a FunctionStatement and conditionally create the function foo. In those browsers, calling foo will throw an error.Letterperfect

© 2022 - 2024 — McMap. All rights reserved.