When I can't fully explain something I try to go back and understand it better and often create notes. These are my notes from trying to further explain both JavaScript's execution context and lexical scope.
Global Execution Context
When your code initially runs, JavaScript creates what is called a Global Execution Context.
This context gives us access to two things right off the bat.
- First is the global
this
- Second is a global object. In the browser this global object is the window.
In the above image I have opened a web page that only has html. There is a single paragraph element.
Yet, in the console I can type in both this and window and see that they are available to me.
The other thing to note is that currently they are the same thing.
this === window
Global Variables
In JavaScript(JS), if we create a variable like var aNewVariable = "Hello world"
this variable will now be globally available.
Let's look at the variable in the console.
Inside my JS panel I add the new variable.
In the console I can call that variable by its name or with the global window object.
If we type in window and open that up we will also see our new variable.
We are now getting in to what is referred as the Lexical Environment or Lexical Scope.
Lexical Environment
Right now our variable is scoped
to the global window object. If we created extra functions or variables those would also be scoped
to our global object.
The lexical scope refers to where the code is written.
Let's look at an example of where a function would not be globally scoped.
I've created a silly 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?
We get undefined.
This function is scoped
to 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 now can see our firstName function and what myName
returns.
In this case firstName
is executed inside our myName
function and returns "Christina" .
More on function execution context
in a bit.
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 we get undefined
and Colonel Brandon
.
What is going on here?
When the JS engine had a first pass at our 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 our variable perfectMatch
as undefined. We do not actually define the variable until later on in the code but we do store the actual variable in memory.
Our 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 we 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 looking at scope and hoisting a bit 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 we actually get is similar to what happened in our previous example.
First we get undefined and then we get
"Colonel Brandon is the perfect match".
When our function is called it is looking inside itself for its variables.
A new execution context, in this case a function or local execution context, executed.
So within the function JavaScript looks for the var
variables and then runs the console.logs.
It allocates perfectMatch to undefined initially so when we run the first
console.log(perfectMatch + " is the perfect match")
it returns undefined
.
We then define it with var perfectMatch = "Colonel Brandon"
And can then see "Colonel Brandon is the perfect match" with the second console.log.
Our 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 our 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"
};
Top comments (3)
Very well, except that I thought you were also gonna touch upon the
let
(and/orconst
) keyword for declaring a variable, thats also if we, in a given interview, mentioned it they would think more highly of ourselves that we know the difference. That islet
variable won't let you access it before its initialization. Although hoisted,let
is not initialized to a default value (ofundefined
).As in your example, it would work similarly if we were to declare
let perfectMatch;
(gets initialized toundefined
) and if we define (initialize) it after the functionconsole.log()
, it would still beundefined
. But, declaring it after its call ->let
throws anReferenceError
and that's whylet
is more helpful in debugging our code.Very well explained. Surprised to see no comments so adding one!
Thank you š