Well at least there wasn't. Before ES6 (ES2015), the only way to declare a variable was to use the var
keyword. And a var
is always scoped to the function in which it is defined. Before ES6, there was no such thing as "block scope" in JavaScript. All variables (and function declarations/expressions) were function scoped. That is, for example the following:
function someFunction() {
for (var i = 0; i < 10; i++) {
// Some operation
}
}
is exactly equal to:
function someFunction() {
var i;
for (i = 0; i < 10; i++) {
// Some operation
}
}
That is, since there was no such thing as "block scope" in JavaScript (before ES6, there is only "function scope"), and since variables are "hoisted", any var
that appears anywhere in a function is the same thing as that var
defined all the way to the top of the function, right after the beginning. Only difference is that assignment takes place on the proper line. That is:
function anotherFunction() {
// Lots of some code
var someVariable = 'someValue';
// Some other code
}
is exactly equal to:
function anotherFunction() {
var someVariable;
// Lots of some code
someVariable = 'someValue';
// Some other code
}
That is, variable declarations are "hoisted" to the top (beginning) of the function, whereas the assignment (the definitions) happens on the line that it appears on the source code.
Another example that demonstrates that there is no "block scope" in JavaScript before ES6:
function anotherFunction() {
// Lots of some code
{
var someVariable = 'someValue';
}
// Some other code
}
is exactly equal to:
function anotherFunction() {
var someVariable;
// Lots of some code
{
someVariable = 'someValue';
}
// Some other code
}
Before ES6, to satisfy the need of a block scope in JavaScript, programmers were using immediately-invoked function expressions (IIFEs). An example:
(function (someParameter, anotherParameter) {
// Some code
})(someArgument, anotherArgument);
This is called an immediately-invoked function expression. It is a function expression (as opposed to being a function declaration) because the function "declaration" is within a pair of parentheses. Being within a pair of parentheses makes it a function expression.
To solve the problem of block scope in JavaScript, ES6 introduced two new keywords for defining variables: let
and const
. They couldn't just change the behavior of var
to behave like let
because doing that would break so many existing codes in new versions of JavaScript (ES6 and above). Hence, to preserve backward-compatibility, they kept the behavior of var
the same as it was before, and they introduced two new keywords for defining block-scoped variables.
A common JavaScript interview question that is based on the function scope property of the var
keyword and closures in JavaScript is as follows:
What will the following code output?
const arr = [10, 12, 15, 21]; for (var i = 0; i < arr.length; i++) { setTimeout(function() { console.log('Index: ' + i + ', element: ' + arr[i]); }, 3000); }
This code is equivalent to:
var i;
const arr = [10, 12, 15, 21];
for (i = 0; i < arr.length; i++) {
setTimeout(function() {
console.log('Index: ' + i + ', element: ' + arr[i]);
}, 3000);
}
That is, on each iteration of the loop, we are registering a function to the event loop that will run at least after 3000 milliseconds. Since the scope of i
is the same for all functions, every function points to the same i
. Each iteration of the loop changes the value of i
. Hence, every function actually accesses the same arr[i]
. The final value of i
is 4, because the loop terminates after the i < arr.length
condition becomes false, and it becomes false when i
becomes 4. Since arr[4]
is undefined
(because there is no such index 4 at arr
), the output is Index: 4, element: undefined
printed 4 times.
Had the original question used let
, instead of var
, then the loop iteration variable would have block-scope (instead of function scope) and hence, each iteration would create a new scope for the loop iteration variable and hence, each function would point to a different variable. This way, the scope of the loop iteration variable on each iteration would have been preserved and hence, the output would have been:
Index: 0, element: 10
Index: 1, element: 12
Index: 2, element: 15
Index: 3, element: 21
So, in short:
- Always use
const
for variables. - If you detect that the variable needs to be "reassigned" for the correct operation of the program, change your code to use
let
, instead ofconst
for that variable. - Never use
var
! If you detect that you need a variable to be available in the whole function (that is, if you want a variable to have "function scope"), simply declare it on the very first line of the function usinglet
, before any blocks, loops, etc, instead of usingvar
for that variable. Example:
function someFunction() {
// We want `someVariable` to have "function scope". Hence, we declare
// it at the very top of the function, before any blocks, etc, using
// the `let` keyword
let someVariable;
// Some other code
}