Do you know what value will be printed on the console when the following piece of code will be executed?
If your answer is 10, then you are right. Here the variable ‘x’ declared (and of course initialized) outside the function ‘test’ has a global scope and that’s why it is accessible anywhere in this scope globally. However, the one declared and initialized inside the ‘test’ function can be accessible only inside that function. So the below code snippet will print 20 on the console upon execution.
Now try to guess the output for this one.
If you have guessed 10 following the scoping logic discussed in the previous examples, then you are unfortunate as 10 is not the correct answer. This time it will print ‘undefined’ on the console. This is because of the combined effect of variable scoping and variable hoisting.
Let’s consider the following code snippet written in C:
10 20 10
10 20 20
10 20 10
In this case, when the control enters into the anonymous function defined and invoked inside the if-block, it creates a new scope. The variable ‘x’ declared inside this scope does not affect the value of the variable ‘x’ declared in the outer scope. Though this is a quite flexible way for creating temporary scopes wherever required, it is not qualified as a good coding style. Hence, to keep the things simple ES6 has introduced two new keywords- ‘let’ and ‘const’, to declare block-scoped variables. When a variable is declared using ‘const’ or ‘let’, it is visible only inside the particular block in which it is declared. For example:
10 20 30 20 10
In this case, when the test() function is invoked the console.log() statement written inside the if-block prints 30 on the console, whereas the one that follows the if-block inside test() function prints 20 on the console. This means that the variable ‘x’ declared and defined using let keyword has no effect on the value of the variable ‘x’ declared outside its scope, i.e. the if-block. The keyword ‘const’ also operates in a similar manner. The only difference between ‘let’ and ‘const’ is- const is a signal that the identifier won’t be reassigned (The reason that the word ‘identifier’ is preferred here over ‘variable’ is since ‘const’ is used to declare the identifier, it is no longer a variable. It is a constant.), whereas let is a signal that the variable may be reassigned.
Now let’s go back to the following example provided previously in this article.
Now let’s look at another example:
undefined undefined 100
This code is interpreted as the following.
Now the important point here is, this variable hoisting mechanism works only for variables declared using var keyword. It does not work for variables or identifiers declared using let and const keywords respectively. Let’s consider the following example.
ReferenceError: x is not defined
Identifiers declared using let or const are not at all hoisted. This makes them inaccessible before their declaration in the original source code.
Here is the answer.
- All variables and constants that need to be visible within the scope of a particular function should be declared using ‘var’ and ‘const’ keywords respectively at the top of that function.
- Inside blocks (conditional statements or loops) variables and constants should be declared on the top of the block using ‘let’ and ‘const’ respectively.
- If in a particular scope multiple variables or constants need to be declared, then declare them in one go by using a single ‘var’ or ‘let’ or ‘const’ keyword with comma separated identifier names,
var x, y, z; // declaring function-scoped variables
let a, b, c; // declaring block-scoped variables
const u, v, w; // declaring block-scoped constants
Though the last point has nothing to do with the consequences of variable hoisting, it is a better practice to keep it in mind while writing code to keep the code clean.
Let’s consider the following example.
foo TypeError: bar is not a function
Though the decision regarding which way one should use to define a function solely depends on developer’s own choice, it is better to keep the following things in mind.
- Define all the functions using function declaration method on the top of their container scope. This not only keeps the code clean but also ensures that all the functions are declared and defined before they are invoked.
- If you must have to define a function using function expression method, then make sure that it is defined before it is invoked in the code. This will eliminate the chance of unexpected outputs or errors that may result due to hoisting.