Javascript function scoping and hoisting
Asked Answered
P

16

104

I just read a great article about JavaScript Scoping and Hoisting by Ben Cherry in which he gives the following example:

var a = 1;

function b() {
    a = 10;
    return;

    function a() {}
}
b();
alert(a);

Using the code above, the browser will alert "1".

I'm still unsure why it returns "1". Some of the things he says come to mind like: All the function declarations are hoisted to the top. You can scope a variable using function. Still doesn't click for me.

Polyclinic answered 21/9, 2011 at 21:23 Comment(0)
T
134

Function hoisting means that functions are moved to the top of their scope. That is,

function b() {  
   a = 10;  
   return;  
   function a() {} 
} 

will be rewritten by the interpeter to this

function b() {
  function a() {}
  a = 10;
  return;
}

Weird, eh?

Also, in this instance,

function a() {}

behaved the same as

var a = function () {};

So, in essence, this is what the code is doing:

var a = 1;                 //defines "a" in global scope
function b() {  
   var a = function () {}; //defines "a" in local scope 
   a = 10;                 //overwrites local variable "a"
   return;      
}       
b();       
alert(a);                 //alerts global variable "a"
Thickset answered 21/9, 2011 at 21:33 Comment(12)
So all the function declarations are eventually assigned to a variable?Polyclinic
@Polyclinic Yes, in Javascript, functions are first class objects, just like strings and numbers. That means they are defined as variables and can be passed to other functions, be stored in arrays, and so on.Thickset
In no way is the function body "rewritten". The various ECMAScript standards clearly state that variable and function declarations are processed before code execution begins. That is, nothing is moved, it's about order of execution (hence my dislike of the term "hoisting", which infers movement or rearrangement). In your re–written code, the declaration var a should be before the function declaration, and the assignment a = 1 should be after. But note that this isn't specified to actually happen by the parser, tokeniser, interpreter, compiler, whatever, it's just an equivalent.Dawes
@Dawes Sure, I guess you could call the description a small "lie to children", but in the end the behavior is the same, whether the code is literally rearranged or just the order of execution is rearranged. What actually happens behind the scenes is more of an academic concern, and might even be implementation-dependent.Thickset
“Also, in this instance, function a() {} behaved the same as var a = function () {}; — this is incorrect in two ways: first, if anything, it would’ve been var a = function a() {}; (the function is actually not anonymous), second, those two forms are not interchangeable, because from var a = function a() {}; only the var a; part would’ve been hoisted. The a = function a() {}; part would still have been behind the return statement. Because the original form is a function declaration and not a function expression, it actually gets hoisted as a whole.Anticline
Function hoisting > var hoistingUnlive
Function hoisting doesn't means that functions are moved to the top of their scope instead, In the creation phase of the Execution Context when the parser runs through the code it sets up memory space for variables and functions (which is called Hoisting). Its not actually moving code to the top of the page, but before your code begins to be executed line by line, the JS engine has set aside the memory space, so when the code begins to execute, those things are actually already sitting into the memory.and therefore we have access to them in a limited way.Reef
@PeterOlson I understand your lie to children comment, but this answer is at top, and we now have let and const, if someone reads this they do not get complete understanding and fail to understand how let and const works, you can have a rigorous definition below your original answer, it will help others.Ricks
@SurajJain This answer was written in 2011, quite a while before let and const came into common use.Thickset
@PeterOlson: I wonder if the behavior of function hoisting changed to accommodate them. If a block contains a let which gets closed over by a function, a separate function object will need to be created each time the block is executed. In the absence of let, it would generally make more sense to create one function object when the outer function is executed.Tomy
Downvoted because it contains a wrong statement as pointed out by @SebastianSimon. Only declarations are hoisted, not assignmentsYang
Rereading my previous comment, I’ll add a minor correction: even in var a = function(){};, the function won’t be anonymous, because that statement is evaluated via NamedEvaluation, so the function’s name will be a, implicitly. It still makes a difference, though: inside named function expressions, as in var a = function a(){};, an immutable binding to a is created; not so in var a = function(){};.Anticline
T
6

What you have to remember is that it parses the whole function and resolves all the variables declarations before executing it. So....

function a() {} 

really becomes

var a = function () {}

var a forces it into a local scope, and variable scope is through the entire function, so the global a variable is still 1 because you have declared a into a local scope by making it a function.

Tog answered 21/9, 2011 at 21:27 Comment(0)
P
5

The function a is hoisted inside function b:

var a = 1; 
function b() { 
   function a() {} 
   a = 10; 
   return;
} 
b(); 
alert(a);

which is almost like using var:

var a = 1; 
function b() { 
   var a = function () {};
   a = 10; 
   return;
} 
b(); 
alert(a);

The function is declared locally, and setting a only happens in the local scope, not the global var.

Proscribe answered 21/9, 2011 at 21:30 Comment(1)
this line "var a = function () {}; " makes every thing clear.. basically JavaScript is dynamic language and "function" is also an object in JavaScript.Bannister
T
3
  1. function declaration function a(){} is hoisted first and it behaves like var a = function () {};, hence in local scope a is created.
  2. If you have two variable with same name (one in global another in local), local variable always get precedence over global variable.
  3. When you set a=10, you are setting the local variable a , not the global one.

Hence, the value of global variable remain same and you get, alerted 1

Trichromat answered 17/5, 2014 at 13:43 Comment(0)
E
2

Suprisingly, none of the answers here mention the relevancy of the Execution Context in the Scope Chain.

The JavaScript Engine wraps the currently executing code in an Execution Context. The base execution context is the global Execution Context. Each time a new function is invoked, a new Execution Context is created and put on the Execution Stack. Think of a Stack Frame sitting on an Invocation Stack in other programming languages. Last in first out. Now each Execution Context has its own Variable Environment and Outer Environment in JavaScript.

I will use the below example as a demonstration.

1) First, we enter the Creation Phase of the global Execution Context. Both the Outer Environment and Variable Environment of the Lexical Environment are created. The Global Object is setup and placed in memory with the special variable 'this' pointing to it. The function a and its code and the variable myVar with an undefined value are placed in memory in the global Variable Environment. it's important to note that function a's code is not executed. It is just placed in memory with function a.

2) Second, it is the Execution Phase of the Execution Context. myVar is no longer an undefined value. It is initialized with value of 1, which is stored in the global Variable Environment. The function a is invoked and a new Execution Context is created.

3) In the function a's Execution Context, it goes through the Creation and Execution Phase of its own Execution Context. It has its own Outer Environment and Variable Environment, thus, its own Lexical Environment. The function b and the variable myVar are stored in its Variable Environment. This Variable Environment is distinct from the global Variable Environment. Since the function a sits lexically (physically in code) on the same level as the global Execution Context, its Outer Environment is the global Execution Context. Thus, if the function a was to refer to a variable that is not in its Variable Environment, it will search the Scope Chain and try to find the variable in the Variable Environment of the global Execution Context.

4) The function b is invoked in function a. A new Execution Context is created. Since it sits lexically in function a, its Outer Environment is a. So when it references myVar, since myVar is not in function b's Variable Environment, it will look in function a's Variable Environment. It finds it there and console.log prints 2. But if the variable was not in function a's Variable Environment, then since function a's Outer Environment is the global Execution Context, then the Scope Chain will continue searching there.

5) After function b and a are finished execution, they are popped from the Execution Stack. The single-threaded JavaScript Engine continues execution at the global Execution Context. It invokes the b function. But there is no b function in the global Variable Environment and there is no other Outer Environment to search in the global Execution Context. Thus an exception is raised by the JavaScript Engine.

function a(){
  function b(){
    console.log(myVar);
  }

  var myVar = 2;
  b();
}

var myVar = 1;
a();
b();
> 2
> Uncaught ReferenceError: b is not defined

The below example shows the Scope Chain in action. In the function b's Execution Context's Variable Environment, there is no myVar. So it searches its Outer Environment, which is the function a. The function a does not have myVar in its Variable Environment either. So the Engine searches function a's Outer Environment, which is the global Execution Context's Outer Environment and myVar is defined there. Hence, the console.log prints 1.

function a(){
  function b(){
    console.log(myVar);
  }

  b();
}

var myVar = 1;
a();
> 1

Regarding Execution Context and the Lexical Environment associated with it, including Outer Environment and Variable Environment, enable the scoping of variables in JavaScript. Even if you invoke the same function multiple times, for each invocation, it will create its own Execution Context. So each Execution Context will have its own copy of the variables in its Variable Environment. There is no sharing of variables.

Evidence answered 13/2, 2019 at 15:51 Comment(0)
M
1

function a() { } is a function statement, which creates an a variable local to the b function.
Variables are created when a function is parsed, regardless of whether the var or function statement gets executed.

a = 10 sets this local variable.

Mary answered 21/9, 2011 at 21:29 Comment(3)
actually a = 10 sets a variable in the global scope when the function b is executed unless you add "use strict" (in such environments as support that directive).Fruge
@Sean: No, because the function statement creates a local identifier.Mary
... and .... you're right. Hadn't realized that particular consequence of function hoisting. Thanks!Fruge
C
1

Hoisting is a concept made for us to make it easier to understand. What actually happens is the declarations are done first with respect to their scopes and the assignments will happen after that(not at the same time).

When the declarations happen, var a, then function b and inside that b scope, function a is declared.

This function a will shadow the variable a coming from the global scope.

After the declarations are done, the values assign will start, the global a will get the value 1 and the a inside function b will get 10. when you do alert(a), it will call the actual global scope variable. This little change to the code will make it more clear

        var a = 1;

    function b() {
        a = 10;
        return a;

        function a() { }
    }

    alert(b());
    alert(a);
Cobble answered 14/4, 2015 at 14:40 Comment(1)
It's curious that so many experts even in a course at codeschool.com refers to hoisting which is nothing more than a simplistic view of what happens, actually hoisting does not happen at all. Ref: 1) developer.mozilla.org/en-US/docs/Glossary/Hoisting 2) Chapter 5 of Secrets of the JavaScript Ninja 2/e by john resig, bear bebeault, josip marasVentricle
T
1

What is the bone of contention in this small snippet of code?

Case 1:

Include function a(){} definition inside the body of function b as follows. logs value of a = 1

var a = 1;
function b() {
  a = 10;
  return;

  function a() {}
}
b();
console.log(a); // logs a = 1

Case 2

Exclude function a(){} definition inside the body of function b as follows. logs value of a = 10

var a = 1;
function b() {
  a = 10;  // overwrites the value of global 'var a'
  return;
}
b();
console.log(a); // logs a = 10

Observation will help you realise that statement console.log(a) logs the following values.

Case 1 : a = 1

Case 2 : a = 10

Posits

  1. var a has been defined and declared lexically in the global scope.
  2. a=10 This statement is reassigning value to 10, it lexically sits inside the function b.

Explanation of both the cases

Because of function definition with name property a is same as the variable a. The variable a inside the function body b becomes a local variable. The previous line implies that the global value of a remains intact and the local value of a is updated to 10.

So, what we intend to say is that the code below

var a = 1;
function b() {
  a = 10;
  return;

  function a() {}
}
b();
console.log(a); // logs a = 1

It is interpreted by the JS interpreter as follows.

var a = 1;
function b() {
  function a() {}
  a = 10;
  return;


}
b();
console.log(a); // logs a = 1

However, when we remove the function a(){} definition, the value of 'a' declared and defined outside the function b, that value gets overwritten and it changes to 10 in case 2. The value gets overwritten because a=10 refers to the global declaration and if it were to be declared locally we must have written var a = 10;.

var a = 1;
function b() {
  var a = 10; // here var a is declared and defined locally because it uses a var keyword. 
  return;
}
b();
console.log(a); // logs a = 1

We can clarify our doubt further by changing the name property in function a(){} definition to some other name than 'a'

var a = 1;
function b() {
  a = 10; // here var a is declared and defined locally because it uses a var keyword. 
  return;

  function foo() {}
}
b();
console.log(a); // logs a = 1
Toweling answered 20/4, 2017 at 16:26 Comment(0)
R
0

It is happening because of the Variable name is same as the function name means "a". Thus due to Javascript hoisting it try to solve the naming conflict and it will return a = 1.

I was also confused about this until i read this post on "JavaScript Hoisting" http://www.ufthelp.com/2014/11/JavaScript-Hoisting.html

Hope it helps.

Reservist answered 16/11, 2014 at 12:55 Comment(0)
W
0

Here's my recap of the answer with more annotation and an acompaniying fiddle to play around with.

// hoisting_example.js

// top of scope ie. global var a = 1
var a = 1;

// new scope due to js' functional (not block) level scope
function b() {
    a = 10; // if the function 'a' didn't exist in this scope, global a = 10
  return; // the return illustrates that function 'a' is hoisted to top
  function a(){}; // 'a' will be hoisted to top as var a = function(){};
}

// exec 'b' and you would expect to see a = 10 in subsequent alert
// but the interpreter acutally 'hoisted' the function 'a' within 'b' 
// and in doing so, created a new named variable 'a' 
// which is a function within b's scope
b();

// a will alert 1, see comment above
alert(a);

https://jsfiddle.net/adjavaherian/fffpxjx7/

Wendolyn answered 9/5, 2016 at 18:41 Comment(0)
C
0

Long Post!

But it will clear the air!

The way Java Script works is that it involves a two step process:

  1. Compilation(so to speak) - This step registers variables and function declarations and their respective scope. It does not involve evaluating function expression: var a = function(){} or variable expression (like assigning 3 to x in case of var x =3; which is nothing but the evaluation of R.H.S part.)

  2. Interpreter: This is the execution/evaluation part.

Check the output of below code to get an understanding:

//b() can be called here!
//c() cannot be called.
console.log("a is " + a);
console.log("b is " + b);
console.log("c is " + c);
var a = 1;
console.log("Now, a is " + a);
var c = function() {};
console.log("Now c is " + c);

function b() {
  //cannot write the below line:
  //console.log(e); 
  //since e is not declared.
  e = 10; //Java script interpreter after traversing from this function scope chain to global scope, is unable to find this variable and eventually initialises it with value 10 in global scope.
  console.log("e is " + e) //  works!
  console.log("f is " + f);
  var f = 7;
  console.log("Now f is " + f);
  console.log("d is " + d);
  return;

  function d() {}
}
b();
console.log(a);

Lets break it:

  1. In the compilation phase, 'a' would be registered under global scope with value 'undefined'. Same goes for 'c', its value at this moment would be 'undefined' and not the 'function()'. 'b' would be registered as a function in the global scope. Inside b's scope, 'f' would be registered as a variable which would be undefined at this moment and function 'd' would be registered.

  2. When interpreter runs, declared variables and function() (and not expressions) can be accessed before the interpreter reaches the actual expression line. So, variables would be printed 'undefined' and declared anonymous function can be called earlier. However, trying to access undeclared variable before its expression initialisation would result in an error like:

console.log(e)
e = 3;

Now, what happens when you have variable and function declaration with same name.

Answer is - functions are always hoisted before and if the same name variable is declared, it is treated as duplicate and ignored. Remember, order does not matter. Functions are always given precedence. But during evaluation phase you can change the variable reference to anything (It stores whatever was the last assignment) Have a look at the below code:

var a = 1;
console.log("a is " + a);

function b() {
  console.log("a inside the function b is " + a); //interpreter finds                                'a' as function() in current scope. No need to go outside the scope to find 'a'.
  a = 3; //a changed
  console.log("Now a is " + a);
  return;

  function a() {}
}
var a; //treated as duplicate and ignored.
b();
console.log("a is still " + a + " in global scope"); //This is global scope a.
Character answered 21/4, 2017 at 4:29 Comment(0)
A
0

Hoisting In JavaScript means, variable declarations are executed through out the program before any code is executed. Therefore declaring a variable anywhere in the code is equivalent to declaring it at the beginning.

Amena answered 14/2, 2018 at 22:13 Comment(0)
F
0

Its all depends on the scope of variable 'a'. Let me explain by creating scopes as images.

Here JavaScript will create 3 scopes.

i) Global scope. ii) Function b() scope. iii) Function a() scope.

enter image description here

Its clear when you call 'alert' method scope belongs to Global that time, so it will pick value of variable 'a' from Global scope only that is 1.

Flotation answered 28/2, 2018 at 15:51 Comment(0)
C
0

Hoisting is behavioural concept of JavaScript. Hoisting (say moving) is concept that explains how and where variables should be declared.

In JavaScript, a variable can be declared after it has been used because Function declarations and variable declarations are always moved (“hoisted”) invisibly to the top of their containing scope by the JavaScript interpreter.

We encounter two types of hoisting in most cases.

1.Variable declaration hoisting

Lets understand this by this piece of code.

 a = 5; // Assign 5 to a
 elem = document.getElementById("demo"); // Find an element 
 elem.innerHTML = a;                     // Display a in the element
 var a; // Declare a
  //output-> 5

Here declaration of variable a will be hosted to top invisibly by the javascript interpreter at the time of compilation. So we were able to get value of a. But this approach of declaration of variables is not recommended as we should declare variables to top already like this.

 var a = 5; // Assign and declare 5 to a
 elem = document.getElementById("demo"); // Find an element 
 elem.innerHTML = a;                     // Display a in the element
  // output -> 5

consider another example.

  function foo() {
     console.log(x)
     var x = 1;
 }

is actually interpreted like this:

  function foo() {
     var x;
     console.log(x)
     x = 1;
  }

In this case x will be undefined

It does not matter if the code has executed which contains the declaration of variable. Consider this example.

  function foo() {
     if (false) {
         var a = 1;
     }
     return;
     var b = 1;
  }

This function turns out to be like this.

  function foo() {
      var a, b;
      if (false) {
        a = 1;
     }
     return;
     b = 1;
  }

In variable declaration only variable definition hoists, not the assignment.

  1. Function declaration hoisting

Unlike the variable hoisting the function body or assigned value will also be hoisted. Consider this code

 function demo() {
     foo(); // this will give error because it is variable hoisting
     bar(); // "this will run!" as it is function hoisting
     var foo = function () {
         alert("this would not run!!");
     }
     function bar() { 
         alert("this will run!!");
     }
 }
 demo();

Now as we understood both variable and function hoisting, let's understand this code now.

var a = 1;
function b() {
  a = 10;
  return;
   function a() {}
}
b();
alert(a);

This code will turn out to be like this.

var a = 1;                 //defines "a" in global scope
 function b() {  
   var a = function () {}; //defines "a" in local scope 
    a = 10;                 //overwrites local variable "a"
    return;      
 }       
 b();       
 alert(a); 

The function a() will have local scope inside b(). a() will be moved to top while interpreting the code with its definition (only in case of function hoisting) so a now will have local scope and therefore will not affect the global scope of a while having its own scope inside function b().

Cassowary answered 1/5, 2018 at 7:20 Comment(0)
L
0

From my piece of knowledge, hoisting happens with the variable declaration and function declaration, for example:

a = 7;
var a;
console.log(a) 

What happens inside JavaScript's engine:

var a;
a = 7;
console.log(a);
// 7

Or:

console.log(square(7)); // Output: 49
function square(n) { return n * n; }

It will become:

function square(n) { return n * n; }
console.log(square(7)); // 49

But assignments such as variable assigment, function expression assignment will not be hoisted: For example:

console.log(x);
var x = 7; // undefined

It may become like this:

var x;
console.log(x); // undefined
x = 7;
Lateshalatest answered 16/8, 2019 at 15:14 Comment(0)
E
0

To describe hosting in javascript in one sentence is variables and functions are hoisted to the top of the scope that they are declared in.

enter image description here

I am assuming you are a beginner, to understand hoisting properly at first we have understood the difference between undefined and ReferenceError

 var v;
 console.log(v);
 console.log(abc);
/*
The output of the above codes are:
undefined
ReferenceError: abc is not defined*/

now in the bellow code what we see? a variable and a function expression is decleard.

<script>
var totalAmo = 8;
var getSum = function(a, b){
      return a+b;
}
</script>

but the real picture with proof that the both variable and function are hoisted on the top of there scope:

console.log(totalAmo);
console.log(getSum(8,9));
var totalAmo = 8;
var getSum = function(a, b){
      return a+b;
}
console.log(totalAmo);
console.log(getSum(9,7));

Output of first two logs are undefined and TypeError: getSum is not a function because both var totalAmo and getSum are hoisted on the top of their scope like bellow

 <script>
        var totalAmo;
        var getSum;

        console.log(totalAmo);
        console.log(getSum(8,9));
        var totalAmo = 8;
        var getSum = function(a, b){
            return a+b;
        }
        console.log(totalAmo);
        console.log(getSum(9,7));
    </script>

But for functions declaration whole functions hoisted on the top of their scope.

console.log(getId());
function getId(){
   return 739373;
}
/* output: 739373, because the whole function hoisted on the top of the scope.*/

Now the same logic goes for those varibale, functions experessions and function declaratoins declared inside functional scope. Key point: they will not be hoisted on the top of the file;

function functionScope(){
            var totalAmo;
            var getSum;

            console.log(totalAmo);
            console.log(getSum(8,9));
            var totalAmo = 8;
            var getSum = function(a, b){
                return a+b;
            }
        }

So, when you use var keyword, variable and function hoisted on the top of there scope (global scope and function scope). What about let and const, const and let are still both aware of the global scope and function scope just like var is, but const and let variables are also aware of another scope called blocked scope. a block scope is present whenever there is a block of code, such as for loop, if else statement, while loop etc.

When we use const and let to declare a variable in these block scope, the variable declaration only will be hoisted on the top of that block that it is in, and it will not be hoisted on the top of the parent function or top of the global scope that it is hoisted.

 function getTotal(){
            let total=0;
            for(var i = 0; i<10; i++){
                let valueToAdd = i;
                var multiplier = 2;
                total += valueToAdd*multiplier;
            }
            return total;
        }

Variables in abobe example will be hoisted like bellow

 function getTotal(){
            let total;
            var multiplier;
            total = 0;
            for(var i = 0; i<10; i++){
                let valueToAdd;
                valueToAdd = i;
                multiplier = 2;
                total += valueToAdd*multiplier;
            }
            return total;
        }
Elmerelmina answered 20/5, 2020 at 7:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.