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.

Siddharth_Vyas
  • 9,487
  • 10
  • 36
  • 67

5 Answers5

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.

Felix Kling
  • 705,106
  • 160
  • 1,004
  • 1,072
  • 1
    I was also under the same impression, but [this code](http://ideone.com/KFYn3c) doesn't work the same way for variables. – thefourtheye Apr 17 '14 at 05:55
  • I wonder if the order is always maintained as seems to be the case here. – Ja͢ck Apr 17 '14 at 05:55
  • @thefourtheye: In which browser does the code contradict? What output do you get? – Felix Kling Apr 17 '14 at 05:57
  • @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? – thefourtheye Apr 17 '14 at 05:58
  • @FelixKling Also, `contradicts` is not the right word I believe. I updated the comment. – thefourtheye Apr 17 '14 at 05:59
  • @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. – Felix Kling Apr 17 '14 at 06:00
  • @FelixKling So, all `var` will be done at runtime and function declarations will be taken care at compile time. Is that right? – thefourtheye Apr 17 '14 at 06:01
  • @FelixKling—[Kanagax](http://kangax.github.io/nfe/) has a pretty good rundown on issues with named function expressions. – RobG Apr 17 '14 at 06:03
  • @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… – RobG Apr 17 '14 at 06:06
  • 3
    @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. – Felix Kling Apr 17 '14 at 06:06
  • @FelixKling Thanks :) Got it. +1 – thefourtheye Apr 17 '14 at 06:09
  • @FelixKling : Thanks a lot for your explaination. – Siddharth_Vyas Apr 17 '14 at 06:15
  • @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. – Felix Kling Apr 17 '14 at 06:17
  • @FelixKling : Not at all. You saved my day :-) Learning JS is a bit challenging. – Siddharth_Vyas Apr 17 '14 at 06:18
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.

6502
  • 104,192
  • 14
  • 145
  • 251
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.

ProllyGeek
  • 14,423
  • 5
  • 44
  • 69
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.

LexJacobs
  • 2,195
  • 13
  • 21
  • Can you explain the logic behind this? – Siddharth_Vyas Apr 17 '14 at 06:23
  • @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. – Pierre Arlaud Apr 17 '14 at 12:36
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

eiximenis
  • 618
  • 4
  • 8
  • "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*](http://kangax.github.io/nfe/#function-statements) and conditionally create the function *foo*. In those browsers, calling *foo* will throw an error. – RobG Apr 17 '14 at 06:11