In this article I will go through JavaScript’s variable scope and hoisting and some of the peculiarities of both of them. It is important to understand how variable scope and variable hoisting work in JavaScript and if you want to understand JavaScript well, these concept are a must.
Variables are a fundamental part of any programming languages, and are among the first and most important concepts beginners learn. There are a number of different properties of variables, as well as several rules which must be followed when naming them in JavaScript.
In JavaScript, you can use three keywords when declaring a variable — var, let, and const — and each one affects how the code will interpret the variable differently.
The differences between the three are based on scope, hoisting, and reassignment.
Keyword | Scope | Hoisting | Can be reassigned | Can be redeclared |
---|---|---|---|---|
var | Function | Yes | Yes | Yes |
let | Block | No | Yes | No |
const | Block | No | No | No |
A variable’s scope is the context in which the variable exists. The scope specifies from where you can access a variable and whether you have access to the variable in that context and a variable declared outside of a function belongs to the global scope, and is therefore accessible from anywhere in your code. Each function has its own scope, and variable declared with keyword var within that function is only accessible from that function and any nested functions, and variables declared with keyword let or const within that function is only accessible from the block within the function where it is declared. :-)
We can summarize this and say JavaScript has variables that are either;
Variables declared with the var keyword has function scope as seen in the example below.
// Declare a variable in the global scope
var name = "Henrik"
function showName() {
// Declare a local variable with function scope
var name = "Lars"
console.log(name) // Lars
}
console.log(name) // Henrik
Variables declared with the let, (or const) keyword has block scope as seen in the example below:
// Declare a variable in the global scope
var name = "Henrik"
// the curly braces block creates a local context
if (name) {
// declare a variable with block level scope
let name = "Lars"
console.log(name) // Lars: in the block scope
}
console.log(name) // Henrik: global scope
Always declare your local variables before you use them. In fact, you should use a linter to check your code for syntax errors and style guides. Here is the trouble with not declaring local variables:
// declare a variable in the global context
var name = "Michael Jackson"
function showCelebrityName() {
console.log(name)
}
function showOrdinaryPersonName() {
// If you do not declare your variables
// They will be a part of the global scope.
name = "Henrik Grönvall"
console.log(name)
}
showCelebrityName() // Michael Jackson: in global scope
showOrdinaryPersonName() // Henrik Grönvall
showCelebrityName() // Henrik Grönvall: The global variable has changed
If you declare a global and local variable with the same name, the local variable will have priority when you attempt to use the variable inside a function:
// declare a variable in the global context
var name = "Paul"
function users () {
// declare a local variable with function scope
// Here, the name variable is local and it takes precedence
// over the same name variable in the global scope
var name = "Ringo"
// The search for name starts right here inside the function
// before it attempts to look outside the function in the
// global scope
console.log(name)
}
users() // Ringo
All variables declared outside a function are in the global scope. In the browser, which is what we are concerned with as front-end developers, the global context or scope is the window object, or the entire HTML document.
Any variable declared or initialized outside a function is a global variable, and it is therefore available to the entire application. For example:
// declare a variable in the global context
var myName = "Henrik"
// or even this
firstName = "Henrik"
// or
var name // name;
All global variables are attached to the window object. So, all the global variables we just declared can be accessed on the window object like this:
function showAge() {
// Age is a global variable because it was not declared
age = 90
console.log(age);//
}
showAge() // 90
// Age is in the global context, so it is available here, too
console.log(age) // 90
Demonstration of variables that are in the global scope even as they seem otherwise:
// Both firstName variables are in the global scope,
// even though the second one is surrounded by a block {}.
var firstName = "Henrik";
{
var firstName = "Lars";
}
// The second declaration of firstName simply
// re-declares and overwrites the first one
console.log(firstName); // Lars
//
// Another example
//
for (var i = 1; i <= 10; i++) {
console.log (i); // outputs 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
}
// The variable i is a global variable and it is
// accessible in the following function with the
// last value it was assigned above
function aNumber() {
console.log(i);
}
// The variable i in the aNumber function below is
// the global variable i that was changed in the for
// loop above. Its last value was 11, set just before
// the for loop exited:
aNumber(); // 11
Note that all functions in setTimeout are executed in the global scope.
// The use of the "this" object inside the setTimeout
// function refers to the Window object, not to myObj
var highValue = 200
var constantVal = 2
var myObj = {
highValue: 20,
constantVal: 5,
calculateIt: function() {
setTimeout(function() {
console.log(this.constantVal * this.highValue)
}, 2000)
}
}
// The "this" object in the setTimeout function used the
// global highValue and constantVal variables, because the
// reference to "this" in the setTimeout function refers
// to the global window object, not to the myObj object as
// we might expect.
myObj.calculateIt() // 400
// This is an important point to remember.
Yes, it is true, do not pollute the global scope.
Consider this example:
// These two variables are in the global scope
// and they shouldn't be here
var firstName, lastName;
function fullName() {
console.log("Full Name: " + firstName + " " + lastName )
}
This is the improved code with the proper way to avoid polluting the global scope.
// Declare the variables inside the function
// where they are local variables
function fullName() {
var firstName, lastName
console.log ("Full Name: " + firstName + " " + lastName )
}
Note: The function in the last example is also in the global scope.
In most of the examples so far, we’ve used the var keyword to declare a variable, and we have initialized it with a value. After declaring and initializing, we can access or reassign the variable.
Remember this table?
Keyword | Scope | Hoisting | Can be reassigned | Can be redeclared |
---|---|---|---|---|
var | Function | Yes | Yes | Yes |
let | Block | No | Yes | No |
const | Block | No | No | No |
The var keyword hoisting, and as seen below, if we tries to use a variable before it has been declared and initialized, it will return undefined
// Attempt to use a variable before declaring it
console.log(name) // undefined
// Variable assignment
var name = 100
If we omit the var keyword, we are no longer declaring the variable, only initializing it. It will return a ReferenceError and halt the execution of the script.
// Attempt to use a variable before declaring it
console.log(name) // ReferenceError: name is not defined
// Variable assignment without var
name = 100
The reason for this is due to hoisting, a behavior of JavaScript in which variable AND function declarations are moved to the top of their scope. Since only the actual declaration is hoisted, not the initialization, the value in the first example returns undefined.
Another example to iterate and make it clearer.
function showName() {
console.log ("First Name: " + name);
var name = "Ford";
console.log ("Last Name: " + name);
}
showName();
// First Name: undefined
// Last Name: Ford
// The reason undefined prints first is because the
// local variable name was hoisted to the top of
// the function, which means it is this local variable
// that get calls the first time.
// This is how the code is actually processed by the
// JavaScript engine:
function showName () {
// name is hoisted, note that is undefined at this point,
// since the assignment happens below
var name;
console.log ("First Name: " + name); // First Name: undefined
name = "Ford"; // name is assigned a value
// now name is Ford
console.log ("Last Name: " + name); // Last Name: Ford
}
Both function declaration and variable declarations are hoisted to the top of the containing scope, and function declaration takes precedence over variable declarations, but not over variable assignment. As is noted above, variable assignment is not hoisted, and neither is function assignment.
As a reminder, this is a function assignment:
var myFunction = function () {}
Here is a basic example to demonstrate:
// Both the variable and the function are named myName
var myName;
function myName () {
console.log("Henrik")
}
// The function declaration overrides the variable name
console.log(typeof myName) // function
// But in this example, the variable assignment overrides
// the function declaration
// This is the variable assignment (initialization)
// that overrides the function declaration.
var myName = "Henrik";
function myName () {
console.log("Henke");
}
console.log(typeof myName); // string
It is important to note that function expressions, such as the example below, are not hoisted.
var myName = function () {
console.log ("Lars");
}
Variables introduced with the keyword var have the potential of being affected by hoisting, a mechanism in JavaScript in which variable declarations are saved to memory. This may result in undefined variables in one’s code. The introduction of let and const resolves this issue by throwing an error when attempting to use a variable before declaring it or attempting to declare a variable more than once.
Personally I have stopped declaring variables with the keyword var and using only let or const and use 'strict' mode and a linter in you editor to avoid mistakes.