Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ugultopu/880536ef19985eebd319bede0c4e249b to your computer and use it in GitHub Desktop.
Save ugultopu/880536ef19985eebd319bede0c4e249b to your computer and use it in GitHub Desktop.
Explaining the scope of variables in JavaScript

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:

  1. Always use const for variables.
  2. If you detect that the variable needs to be "reassigned" for the correct operation of the program, change your code to use let, instead of const for that variable.
  3. 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 using let, before any blocks, loops, etc, instead of using var 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment