Introduction
In modern days, more and more applications are made online and as we know JavaScript is pivotal for any web application, either simple or rich (RIA). Understanding JavaScript is never more important and JavaScript scope is key to writing bullet-proof code for software professionals who work with web applications mostly.
In this article, I am going to talk through what you should know about JavaScript scoping. We will look into the following topics with an example for each.
- Scope
- Global Scope
- Local Scope
- Function scope
- Lexical/static scope
- Closures
- ‘this’ object and scope
- Changing scope with call()
- Public and private scope
Scope
Scope means the current context of your code. A scope can be global or local. By understanding scope, you will understand where variables and functions/objects are accessible, and you will be able to change the scope of your code. It enables us to write more maintainable code as well as to debug much faster.
Global Scope
We are in global scope when we start writing any piece of JavaScript. For example, when we declare a variable, then the variable is globally defined, which means it is accessible from anywhere in the script.
Example
var app = 'Demo';
console.log(app);
function funcA() {
console.log("inside a function: ", app);
};
funcA();
Explanation
In the above example, the variable app
is defined at global scope. As you can see, the variable is called from global scope and also from inside a function. Because the variable is defined a global level, it is accessible from anywhere within the script. There is typically only one global scope.
The console output is:
Local Scope
When you define a function in the global scope (top level) or within another function (local scope), then the function has its own local scope. The local scope of outer function is accessible to the inner function.
Example 1
1 var globalVar = "Global Scope";
2 var funcG = function () {
3
4 var funcName = 'funcG';
5 console.log(funcName);
6
7 console.log(globalVar);
8 };
9 funcG();
10
11
12 console.log(funcName);
Explanation
The following is the output from the above example 1 where as you can see the variable funcName
is undefined in the last line console.log(funcName);
because it is defined in local scope inside a function where as the global variable globalVar
is accessible in console.log(globalVar);
inside local scope.
Example 2
function funcOuter() {
var outerVar = 'local variable funcOuter();';
function funcInner(){
var innerVar = 'local variable funcInner()';
console.log('inside funcInner : ', innerVar);
console.log('inside funcInner : ', outerVar);
}
console.log(outerVar);
console.log(innerVar);
}
Explanation
The following is the output for example 2 where the innerVar
is undefined because it is defined inside funcInner()
function and inside local scope which is not accessible to outside function.
At the same time, the variable outerVa
r defined in function funcOuter
is accessible inside funcInner()
because outer local scope is accessible in inner local scope.
Function Scope
A scope is not created for each statement, but for each function. When a FOR
or WHILE
statement is used, a new scope is not created. A function means a new scope that’s the thumb rule.
Example
function funcLoop() {
for (i = 0; i < 5; i++) {
console.log('inside loop :', i);
}
console.log('outside loop :', i);
}
funcLoop();
Explanation
The following is the output for the above code and as you can see, the variable i
is defined inside the FOR
statement and is accessible from outside the loop too which proves that there is no scope inside the statement.
Lexical/Static Scope
When you define a function within another function, the inner function has access to the scope in outer function. This is called Lexical or Static scope.
Example 1
var parentFunc = function () {
var param1 = 'parameter 1';
var childFunc = function () {
console.log('The parameter is :', param1);
}
childFunc();
console.log(param1);
}
parentFunc();
Explanation
The output for the above code is shown below. As you can see, the childFunc()
is able to access the var
param1
that is defined in outer function.
The lexical scope works from outer most function to inner most function. It does not work backwards.
Example 2
function outerMost() {
console.log(innerMost);
function outer() {
console.log(innerMost);
function inner() {
console.log(innerMost);
function innerMost() {
var innerMost = 'inner most';
}
}
}
}
outerMost();
Explanation
In example 2, the var<code>
innerMost is defined in innerMost()
function and it is not accessible to outer functions. The output is shown below:
Closures
A closure is an object that returns two things: a function and an environment with it.
Closures tie in very closely with Lexical scope. We can return things inside our scope so that they are available in the parent scope.
We can demonstrate it with the following example.
Example
var echo = function (shout) {
var txt = 'Echoing back : ' + shout;
return function () {
console.log(txt);
}
}
var f1 = echo('Hello');
f1();
Explanation
The function echo()
in the above example returns a function that displays a text using the var txt
. The txt
is defined within the echo()
function. However, as you can see from the following output , the echo()
function is returned with the environment (scope) of var txt
within it.
'this’ object and scope
The keyword ‘this
’ is a very important part of JavaScript programming, and understanding how value of ‘this
’ is assigned in different places in code and the way how the functions are called is absolutely paramount to write a bug-free code.
The value of ‘this
’ at the very top level is the window
object. However, the value of ‘this
’ is bound to have different values by invoking functions differently. We can easily demonstrate using an example.
Example
var myFunc = function () {
console.log(this);
};
myFunc();
var myObj = {};
myObj.method1 = function () {
console.log(this);
};
myObj.method1();
<p class='scope'>Click</p>
<script type="text/javascript">
var p = document.querySelector('.scope');
var display = function () {
console.log(this);
};
p.addEventListener('click', display, false);
Explanation
The first function myFunc()
returns the object window as 'this'
which is shown below in the output.
The second function myObj.method1()
returns the object myObj
for 'this'
because the function method1()
is part of the object myObj
. The output is shown below:
Thirdly, the function display()
returns the <p>
element for 'this'
because the click()
function is called on the element <p>.
The output is shown below:
Changing the scope with call()
The scope of 'this
' can be changed using the function call()
in our code.
Let’s consider the following example:
<ul>
<li>Red</li>
<li>Green</li>
<li>Yellow</li>
<li>Blue</li>
</ul>
<script type="text/javascript">
var listItems = document.querySelectorAll('ul li');
for (var l = 0; l < listItems.length; l++) {
console.log(this);
}
</script>
In the ‘for
’ loop, the developer is trying to log the list items values. But the actual values that are being logged in console are window
object because ‘this
’ refers to window
object in the scope. The console output and the browser output are given below:
Of course, we can get the value of list item using array index as below:
console.log(listItems[l]);
In order to demonstrate how a scope can be changed by changing the context, we are going to use the call ()
method in the above code. The same code is rewritten using call ()
method and we can see how it changes the context of the code and its scope.
var listItems = document.querySelectorAll('ul li');
for (var l = 0; l < listItems.length; l++) {
(function () {
console.log(this);
}).call(listItems[l]);
}
The output of the above is shown below. This time, as you can see, the <li>
elements are returns for 'this
' object. This way, the function call()
is very powerful in certain situations.
Public and Private Scope
Inherently, there is no public
and private
scope in JavaScript unlike in other languages but we can emulate them using module pattern.
Let's see the following example:
var myModule = (function () {
var _myPrivateMethod = function () {
console.log('_myPrivateMethod()');
}
return {
methodOne: function () {
console.log('methodOne()');
}
};
})();
myModule.methodOne();
myModule._myPrivateMethod();
In the above code, the line myModule._myPrivateMethod();<font color="#111111" face="Segoe UI"> </font>
would fail because the function _myPrivateMethod()
is accessible because it is a private
method to myModule
.
However, the line of code myModule.methodOne();
would succeed though because the method is made public
with the return
statement.
Points of Interest
It is worth having a read on bind()
and apply()
that are similar to call()
with slight variation. It is also worth reading more in detail about module
pattern because it is key to Angular JS.