Scope, Lexical Environment and Scope Chain in JavaScript

Anıl Akgüneş
8 min readNov 19, 2021

--

Scope

Scope is one of the most important JavaScript concepts and it is one of the core features to help us to understand other important concepts such as closures and lexical environment as well.

Basically scope manages accessibility of variables. Scope provides us two important benefits. One of them is to have a more modular and secure code. We can create some scopes or areas that are not accessible and not able to be modified from outside. The other benefits is to avoid the name collision for variables.

Before getting into the details; lets take a look at the types of scope;

  • Global Scope
  • Local Scopes (Block Scope & Function Scope)

Now let’s dive into the key word in the definition “accessibility”.

Global Scope

When we say that a variable having global scope, actually we means it is not wrapped with any brackets ( { ) and it is accessible from everywhere.

example for global scope
example for global scope

In the example above, the variable x is not wrapped with any brackets so it has a global scope and it can accessible from everywhere.

Local Scope

Basically, a variable having local scope is only accessible inside a local area which it is defined. It can not be accessible from outside. So our next question is what creates or makes a local scope ?. Actually two things creates local scope; one is any brackets {} from if statement, functions or loops vs. and the other thing is functions. That’s why local scope are consist of two things: block and function scope.

Block scope is created with brackets so i mean when we write for example for loops, if statements or functions, we use brackets and these brackets create a local scope inside of it. So a variable defined in these brackets can only accessible from inside and not accessible from the outside of the brackets.

example for block scope

Basically the x variable is defined in brackets and it is not accessible from outside that’s why we are getting reference error here. However we have one exception that doesn’t obey these block scope, which are variables defined with “var” keyword.

example for function scope

Function scope: If we define the variable with “var” keyword, it will not obey the block scope. It only obeys the function scope. Function scope is basically the inside of brackets created by functions.

example for function scope

Well, it may look a bit confusing. Let’s learn the reason behind it. Before ES6, we have only one keyword to define a variable “var”. So in that time we have one type local scope, which is created by functions. With ES6, they want to make the code more modular so they added two keywords more to declare a variable “let” and “const”. The variables declared with “let” and “const” obeyed all brackets and are not accessible from outside. In this way with “let” and “const” we can have block scope now. Also with ES6, using “let” and “const” is best practice now and it is recommended not to use “var” anymore unless you are totally aware of what you are doing.

Nested Scope

The scopes can be nested. In this example we have 3 scopes. One is global scope, the other is foo function scope and if statement scope. In nested scopes, we have two terms that we often use: inner scope and outer scope.

Inner Scope: If statement scope is an inner scope of foo function scope.

Outer Scope: foo function scope is the outer scope of if statement scope.

Actually it is pretty intuitive. Global scope is an outer scope of local scope in someway as well.

example for nested scope

Static(Lexical) vs Dynamic Scope in a Programming Language

I think so far, we have covered the accessibility of variables. Now we can figure out whether we can access a variable or not by checking the scopes of it.

Let’s focus on the resolving of variables. We said that at first the scope enables us to define variables having the same name or “identifier” if use the EcmaScript term. The question is how the JavaScript choose the correct values for these variables.

There are two ways to choose a value for a variable in scopes. Generally programming languages choose one of these two methods: Dynamic Scoping or Static Scoping.

The languages which are C, Java, JavaScript, Python and Ruby are the just some languages using the Static Scope. On the other hand, Perl is an example for a language using the dynamic scoping.

The key difference between static and dynamic scope is pretty straightforward. When resolving, static scope cares the where the variable is declared physically. That environment matters for static scope. On the other hand, dynamic scope cares the where the function/caller is invoked, so the revolving actually is not static, it depend on where i invoke not the function itself.

Let’s check the example just below. When foo function is invoked, it needs to resolve the value of x and y somehow. x variable is the key in this example. We have declared two x variables, one is in global scope equals to 10 and the other one is 99 in a local scope. So which is the one the foo function will choose for its x ?.

Well it is a JavaScript code and we know that it is static scoped. So the important thing is, as we said above, the where the variables are declared. if foo function can’t find any x in its scope it will look at the outer scope and it finds the x = 10 in global scope and choose it. Static scope doesn’t care where the function is invoked so the x in boo function is useless for us here.

example for dynamic&static scope

However if it comes to dynamic scope, the things change. Dynamic scope cares where the function is called so the value for x will be 99 in this case. Dynamic scope uses a stack structure and keep values for a variable like x= [10, 20] and choose it from that stack.

The word “static” is related with determining the scope of a variable at the parsing stage. So it means, before we execute our code, for every variable we can find the scope of them. The “dynamic” word means, at parsing stage, the scope for a variable is not static so i can’t find the scope because it is dynamic and it may change in runtime. As a conclusion, the words “dynamic” and “static” words for scope, comes from their behavior at the parsing stage.

Not: parsing is a component of a compiler/interpreter. Parsing basically checks the grammar/syntax of code and create a more suitable data structure for compiler/interpreter that converts the code so that machine can understand.

What is Scope Chain ?

Scope chain is an actually self-explanatory term, it basically means scopes we have discussed so far somehow establish a connection with each other. To completely understand this, firstly we need to discuss briefly a few things more : Execution Context and Lexical Environment.

Execution context basically is a wrapper to manage the code that is running. Every code block creates an execution context while executing. As you see in the gif below, for every code block a rectangular is created in the stack.

Not: Stack here means Call Stack which is a part of V8 engine and it is responsible for the order of executing the code.

The every rectangular created on the right is an execution context. Every execution context creates a Lexical Environment for variables. The lexical environment is basically data structure keeps the variable and their value reference in memory so that it can easily find it for execution context. Lexical environment consist of two parts : Environmental Record and a reference to outer lexical environment. While environmental record keeps the local variable data, outer reference keeps a reference for parent’s lexical environment. Let try to understand this in an example.

Here we have a code snippet and try to demonstrate how the call stack execute the code blocks.

When execution context is created, there are other things created as well but we will stick with only lexical environment for the sake of simplicity. For every code block, a lexical environment is created which keeps local variable data and a reference to parent’s lexical environment. The key part here is the outer reference. The outer reference checks the parents lexical environment and make it accessible for the its own scope. The scope of a code block can reach all its parents lexical environment so it means it can use its parent’s variables. In this way, we have a chain actually, chain of all parent’s scope.

Basically scope chain means we can access and use our parents variables, it doesn’t matter how deep or nested the function is, the outer reference will provide us all parent’s scope till the global scope.

There is one quite important thing is that the outer reference decide its parent lexically or in other words physically. I mean if we remember the static scope, the place where we write code is matter not where we call it. So in basic words, outer reference for first function is the beyonds of its brackets ({}).

I hope everything is clear for you. If anythings sounds weird or wrong, please let me know.

References

Dmitry Soshnikov -http://dmitrysoshnikov.com/ecmascript/es5-chapter-3-1-lexical-environments-common-theory/#scope

Komolafe Tolulope -https://medium.com/backticks-tildes/understanding-hoisting-and-scoping-in-javascript-39ea7b41e31

Sukhjinder Arora -https://blog.bitsrc.io/understanding-scope-and-scope-chain-in-javascript-f6637978cf53

Dmitri Pavlutin -https://dmitripavlutin.com/javascript-scope/

--

--