Javascript – Execution context and scope

Note: This is part of Javascript Learning Path

Understanding how the execution context works is the first major challenge for a beginner JS developer.

By the end of this reading, you will be way more comfortable around terms like variable scope, hoisting, variable lookup, scope chain and others. It’s not easy for a beginner to grasp all this concepts but trust me, it’s worth it!


What do you know about execution context and scope?

Before we begin, let’s start with a fun example. Have a look at the code bellow and without scrolling down, try to predict what the program will print on each of the 6 lines:

var myString = "I'm outside";

function f() {
    console.log(myString); // 2
    var myString = "I'm in function";
    console.log(myString); // 3
 
    for (var i = 0; i < 1; ++i) {
        var myString = "I'm in for";
        console.log(myString); // 4
    }
    console.log(myString); // 5
 }

console.log(myString); // 1 
f();
console.log(myString); // 6

Check the full example here JS Bin on jsbin.com

Ok, let’s see the results:

1. "I'm outside" 
2. undefined
3. "I'm in function"
4. "I'm in for"
5. "I'm in for" 
6. "I'm outside"

Yep, you definitely can’t get bored with Javascript. If you got 6/6 you are a true master of context and the following read should be a piece of cake. If not, keep in mind the number you got it wrong. We will reference them later in explanations.


What is execution context?

The execution context is basically the environment in which the code is executed. At any point in time, the active execution context can be one of the following:

  • global code
    •  the outermost one
    • the object representing this context may differ depending on the host environment
    • in the browser – global scope is represented by window object
  • function code
    • each function call has its own execution context
    • whenever a function is called (in the global code or inside another function) – a new context is created
    • when the call is ended, the execution context associated with that call is destroyed
  • eval code
    • running an eval() command creates a new execution context
    • using eval() is not recommended in general due to performance and possible security issues, therefore I will ignore it for the rest of the article

Every execution context has associated a variable object which all of its variables and function exist – is not accessible by code but it’s used behind the scenes to handle data.


Execution flow in Javascript

The execution flow in JS behaves like a stack. At the bottom of the stack is always the global context. The global context will be destroyed only when the program is ended.

Every time a new function is called, a new context is created and add it on top of the stack. It will remain there until the execution of that function is ended. At any point in time, in stack will be the functions which are called and haven’t finish execution yet.

Let’s see an example:

function printer(index) {
    if (index == 0) {
        return;
    }
    printer(index-1);
    console.log(index);
}

function main(index) {
    printer(index); 
}

main(2);

How the execution flow will look when this code is executed?

  •  since the program starts and until calling the main(2) function we have only the global context in the stack

Screen Shot 2017-09-07 at 2.20.53 PM.png

  •  when calling main(2), another context is added on top

Screen Shot 2017-09-07 at 2.22.48 PM.png

  • inside the main function, we called the printer function so we add a new context on top

Screen Shot 2017-09-07 at 2.26.05 PM.png

  • we entered  the printer function, we pass the if (since index is 2 in this case) and move on to find another call of the printer -> another context is created
  • !!! a new context is created even the function calls itself – because the context is link to the function call, not the function itself
  • Screen Shot 2017-09-07 at 2.25.36 PM.png the printer function is called this time with the index = 1, so in the new context, index will be 1
  • we call the printer again with index 0 -> new context is created

Screen Shot 2017-09-07 at 2.25.20 PM.png

  • this time, when reaching the if (index == 0) statement, the function returns, so the stack pops the last entry

Screen Shot 2017-09-07 at 2.25.36 PM.png

  • we go back to printer function where index is 1 and index is printed
  • execution is over in this function as well, so the context is pop

Screen Shot 2017-09-07 at 2.26.05 PM.png

  • we are now in the first call of function printer, with index = 2
  • 2 is printed and the function ends -> we go back to main

Screen Shot 2017-09-07 at 2.22.48 PM.png

  • we print the value of index from main, which is 2 and the call ends

Screen Shot 2017-09-07 at 2.20.53 PM.png

  • the global execution is the only one left after the main is finished – this will pe pop only when the program itself is terminated

When a function is declared with var is added to the most immediate context available. In a function, that would be function local context.


No block level context

One of the main sources of confusion regarding execution context is the fact that JS doesn’t have block level execution context. For example, in other main stream languages – C++, Java, C# -, if you would declare a variable inside a for (or while, or if or any block), that variable would not be accessible after the for block ends (belongs to the scope of that block). This is not the case in JS hence the possible confusion at (5) in the example above.

EcmaScript2015 introduces the keyword let which declares a variable visible only in that block. Variables declared by let have as their scope the block in which they are defined, as well as in any contained sub-blocks. In this way, let works very much like var. The main difference is that the scope of a var variable is the entire enclosing function.

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

Variable scope

The scope determines the accessibility  (visibility) of a variable. In Javascript, there are 2 types of scope:

  • global – the exterior code
  • local – inside a function

Whenever a function is called, a new scope is created. Local variables have local scope: They can only be accessed within the function.

When a variable is declared using var, is automatically added to the most immediate scope available. In a function, the most immediate one would be to function’s local context. Since local variables are only recognized inside their functions, variables with the same name can be used in different functions.

Each function has its own scope, and any variable declared within that function is only accessible to that function. However, a global variable can be used inside a function, unless another variable with the same identifier is used in that function.

But what happens if we have a nested function? Let’s see an example:

var global = 1;
var mainVariable = 0;

function main() {
    var mainVariable = 2;
    function nested() { 
        var nestedVariable = 3;
        console.log(nestedVariable);
        console.log(mainVariable); 
        console.log(global);
   }
   nested();
}

main();

When we run this program, it will print 1 2 and 3. We can see that the nested function has access to the globalVariable and the mainVariable from main(), even though they don’t belong to the same context. How is that possible? The answer is scope chain.


Scope chain

When code is executed in a context, a scope chain of variable is created. The purpose of the scope chain is to provide ordered access to all variables and function than en execution context has access to. The front of the scope chain is always the variable object of the context whose code is executing. The back of the scope chain is always the global scope (to whom anyone has access to).

In this example, when we reach the console.log statements, we have the following scope chain:

global scope -> main() function scope -> nested() function scope

In the nested function we have access to all variables from main() and global. However, this can’t go other way around. Main cannot see the variables inside nested. This is a very powerful feature.

Whenever encounters an identifier, the JS engine search for it in the entire block chain, starting with the most recent one in the chain. This process is called variable lookup. Let’s see it in action for the example above

  • console.log(nestedVariable);
    • the JS engine looks for the identifier ‘nestedVariable’ in the nested() function scope first – it finds it there and use it for printing
  • console.log(mainVariable)
    • we look for the mainVariable name in nested() scope – nothing there
    • we look for it in the next scope in chain – main() scope – and it’s there

You may notice here that in the block chain there is another variable named mainVariable (in the global scope). However, that variable is unreachable from nested() function and main().

!! The variable lookup ends when the first identifier is found. If you declared a variable locally with the same name as a global one, you will loose access to the global one

  • console.log(globalVariable)
    • no identifier found on nested() scope
    • no identifier fount on main() scope
    • the identifier is found in the global scope

If the JS engine reaches the end of the scope chain and still couldn’t find a matching name, it returns undefined.


Variable Hosting

Let’s go back to the first example of the article. What happened at print number (2)?

The variable is declared globally and is declared again inside the function one line bellow. However, the result is undefined. 

The short answer is: variable hosting. As we already stated before, if we declare a variable with var inside a function, it will be attached to the local scope and we won’t have access to the global variable with the same name anymore.

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). So it doesn’t matter where inside a function we declare a variable, the variable belongs to that scope from the beginning of execution context.

However, JavaScript only hoists declarations, not initializations. So until an explicit add a value to that variable, it will be undefined.

In that example, we are using myString in the first line of function f(). We know the myString was hoisted, so it belongs to the f() scope. At variable lookup, the identifier it’s found there. Because an explicit value was not added to it there, it’s still undefined.


Conclusion

And that was it. We had passed the first real challenge in this roadmap.

I know a lot of developers that complain about Javascript being weird and unpredictable. This is because they assume that JS should behave exactly like other languages – especially Java -. There are some core differences between these 2 and what we have discussed in this article is one of them. Once you understand these core concepts, the rest will be a piece of cake.

Now let’s look again at the first example. Things should make sense now:

1. "I'm outside"  // variable from global scope

2. undefined  // hoisted local variable from function scope 
              // the value is not added yet, so it's undefined

3. "I'm in function" // same variable as above, but this time a 
                     // value was explicitly added

4. "I'm in for" // same local variable, initialized again

5. "I'm in for" // since we don't have block level scope, the same 
                // local variable is printed

6. "I'm outside" // the variable from the global scope again (like 1)

Further reading:

Scotch.io Understanding scope in javascript 

ryanmorr.com: Understanding scope and context in javascript

Javascriptissexy.com Variable scope and hoisting explained

Thank you for your time!

Please subscribe for more weekly web wisdom!

4 thoughts on “Javascript – Execution context and scope

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s