JavaScript: Execution Context and Lexical Scope

Photo by Growtika on Unsplash

JavaScript: Execution Context and Lexical Scope

·

5 min read

This technical explanation seeks to explain JavaScript's execution context and lexical scope.

Global Execution Context

When your JavaScript code initially runs, it creates what is called a Global Execution Context.

This context gives you access to two things:

  • First is the global this

  • The second is a global object. In the browser, this global object is the window.

Example of global this and global window object in JavaScript

In the above image, I have opened a web page that only has HTML. You can see a single paragraph element on the webpage. Yet, in the console, if you type this and press enter, then type window and press enter you will see that both are available. This is because currently, they are the same thing.

this === window

Global Variables

In JavaScript(JS), if you create a variable var aNewVariable = "Hello world"

the variable will become globally available. Look at the variable in the console.

Inside this JS panel, I added the new variable.

Adding a variable to my JavaScript called aNewVariable

In the console, I call that variable by its name or with the global window object. If you type in window and open up the console you will also see the new variable.

Example code:

https://codepen.io/cgorton/pen/KKdWqQE

This is an example of JavaScript's Lexical Environment or Lexical Scope.

Lexical Environment

In the previous example, the variable is scoped to the global window object. If you create extra functions or variables those would also be scoped to the global object.

The lexical scope refers to where the code is written.

Here is an example of where a function would not be globally scoped.

In this example, I've created a function called myName() that returns another function called firstName(). If I were to go to the console and type firstName() what do you think would happen?

Alt Text

You will see undefined.

This function is scoped for the myName() function and is not available on the global object.

myName() is available on the global window object and when we type in myName() we can now see our firstName function and what myName returns.

Viewing our scoped function by looking at our global function myName

In this case, firstName is executed inside our myName function and returns "Christina" .

Example code:
https://codepen.io/cgorton/pen/abvJGpw

More on function execution context in a moment.

Hoisting

"Hoisting is a JavaScript mechanism where variables and function declarations are moved to the top of their scope before code execution." - Mabishi Wakio

If you have a variable declared with var or a function declaration, JavaScript will hoist it or allocate memory for it after the first run-through of your code.

So if your code looked something like this:

console.log(perfectMatch)
austenCharacter();

var perfectMatch = "Willoughby"

function austenCharacter() {
  console.log("Colonel Brandon")
}

What would you expect to see in the console?

In the console, you will see undefined and Colonel Brandon.

So, what is going on here?

When the JS engine had a first pass at the code it looked for all of the var variables and functions and allocated memory to them.

So in the case of perfectMatch , when the code runs the first time it stores the variable perfectMatch as undefined. The variable is not actually definied until later on in the code, but you do store the actual variable in memory.

The function is also hoisted or stored in memory but because it is a complete function we can execute the code inside even if austenCharacter(); is called before the function is defined in our code.

Because it has been hoisted , JavaScript has kept this function in memory, and wherever you then place the function call austenCharacter(); , no longer matters.

Local execution context

Another type of execution context happens within functions.

When a function is called, a new execution context is created.

Below, is a common Javascript interview question surrounding local execution context.

After learning a little about scope and hoisting, what do you think will happen when this code is run?

var perfectMatch = "Willoughby"

var newMatch = function () {
  console.log(perfectMatch + " is the perfect match") // what do we expect?

  var perfectMatch = "Colonel Brandon"

  console.log(perfectMatch + " is the perfect match") // what do we expect?
};

newMatch()

You might expect the first console.log to be "Willoughby is the perfect match" and the second to be "Colonel Brandon is the perfect match".

What will actually appear is similar to what happened in the previous example.

First, you get undefined and then get

"Colonel Brandon is the perfect match".

Example code:

https://codepen.io/cgorton/pen/OJypOoE

When the function is called, it looks inside itself for the scoped variables.

A new execution context, in this case, a function or local execution context, is executed.

So within the function, JavaScript looks for the var variables and then runs the console.logs.

It allocates the variable perfectMatch to undefined initially. So when you run the first console.log(perfectMatch + " is the perfect match")it returns undefined.

After, var perfectMatch = "Colonel Brandon" is defined.

So the second console.log returns "Colonel Brandon is the perfect match".

The code:

var newMatch = function () {
  console.log(perfectMatch + " is the perfect match") // what do we expect?

  var perfectMatch = "Colonel Brandon"

  console.log(perfectMatch + " is the perfect match") // what do we expect?
};

A representation of the code after hoisting:

var newMatch = function () {
  var perfectMatch = undefined // our hoisted variable
  console.log(perfectMatch + " is the perfect match") // So now this console.log is undefined

  var perfectMatch = "Colonel Brandon" // we now define our variable as "Colonel Brandon"

  console.log(perfectMatch + " is the perfect match")
// Now we can console.log our newly defined variable: 
// "Colonel Brandon is the perfect match"
};

Conclusion

Delving into the intricacies of JavaScript's execution context, lexical scope, and hoisting is more than an academic exercise – it is a valuable tool for mastering the language. Knowledge of these concepts can help developers write robust and efficient JavaScript code.