Hello World Master

Tutorials, articles and quizzes on Software Development

Javascript > Articles

Understanding lexical and block scoping using var and let variable declarations

When learning to write Javascript using ES6, one of the most pronounced features of the language, one of the first things we learn is to replace the variable declaration var with const if the variable is not supposed to change, and let if we want to potentially change it at some point in our code.

var is lexically scoped

The var keyword is lexically scoped, meaning that a variable is stored within the scope within the function it was declared in.

function block() {
  var i = 0;
}

The highest point that i is defined is inside the function it was declared in and any descendant code blocks defined inside the function block.

Note, a code block is anything that has curly braces such as these { }

This includes functions, if statements, switch statements, while loops and for loops.

For example, if a function has an if statement, that ifstatement will have access to the variables defined in function its inside of and the functions outward.

We can also access and modify available lexically scoped variables using loops.

var j = 2;
function blockparent() {
  function block() {
    var i = 0;
    if(i === 0) {
      console.log('I can access i\'s value' + i ); // prints 0
      console.log('I can also access j\s' value + j); // prints 2
    }
    for(i; i <= 4; i++) {
      console.log('I can access and modify i'\s value in a for loop);
    }
   console.log('Here is the result of i after being incremented by for loop: ' + i);
  }
}

When a variable is lexically scoped function nested inside that function that variable was created also have access to that variables of its parent. This rule then continues for the function inside that function and so on.

function block() {
  var i = 0; // i is initialized and defined in here
  console.log(i); // print 0 in the console
  function child_of_block() {
     i = 2; // i is also defined in here.
     console.log(i) // print 2 in the console
    function grandchild_of_block() {
      i = 90;
      console.log(i) // print 90 in the console
    }
  }
  console.log(i) // print 90 in the console
}
console.log(i) // undefined

Even if we created a great_grandchild_of_block function inside our grandchild_of_block function would still have access to i.

Let variable declarations are block scoped

Block scope is defined at the point of a block,

As mentioned earlier, the definition of a block is anything that has curly braces { }

If we declare that variable inside of an if statement, that if statement is the highest point at which we can call that variable.

The easiest way to explain block scope is to show the difference between it and lexical scope. Lets start with lexical scope:

function block() {
  console.log(i) // undefined because i was not declared yet
  if(true) {
    var i = 1;
    console.log(i); // prints 1
  }
  console.log(i); // prints 1
}
console.log(i); // prints undefined

We can see that in lexical scoping, once we go through the if statement, i is now defined in our function block. Our if statement is in the function block() therefore, for lexical scoping, if something is defined in an if statement, we can see it as being defined for our block function and any functions that would nested inside block.

Now lets write the same code, but instead of declaring our variable i using var, we will declare our variable using let:

function block() {
  console.log(i) // undefined because i was not declared yet
  if(true) {
    let i = 1;
    console.log(i); // prints 1
  }
  console.log(i); // prints undefined (unlike var)
}
console.log(i); // prints undefined

The difference we see here is i is not defined after we have left our if statement. This is because with block scoping, variables are defined inside of our if statement, and not in the function itself.

Block scoping follows the rule that variables are defined inside the block at which they’re defined in. Our if statement defines i, not our block function

A simple way to think about is that i is defined within the curly brackets of our if statement and no where else.

We can use block scoping with while loops

function whileLoopBlock() {
  let counter = 0;
  while(counter < 10){
   let j = "I am defined";
   console.log(j); // prints "I am defined"
   counter++;
  }
  console.log(j) // prints undefined;
}

What about for loops?

With for loops things might get a little confusing because of the for loop syntax. If our variable is defined inside of the parenthesis of the for loop like this:

for(let i = 0; i<=10; i++){
}

Then this is not defined inside of the for loop, but is defined inside the block containing the for loop. so if this for loop is inside an if statement, then the variable i is defined inside the if block:

if(true){
  for(let i = 0; i<=10; i++){
    console.log(i); // i is defined here
  }
 console.log(i); // i is also defined here
}

Remember, functions also contain curly braces and like lexical scoping, the descendant functions will also define variables declared in its ancestor functions:

function block() {
  let i = 0; // i is initialized and defined in here
  console.log(i); // print 0 in the console
  function child_of_block() {
     i = 2; // i is also defined in here.
     console.log(i) // print 2 in the console
    function grandchild_of_block() {
      i = 90;
      console.log(i) // print 90 in the console
    }
  }
  console.log(i) // print 90 in the console
}
console.log(i) // undefined

What about const?

const is another way we declare variables using es6 syntax. Const is also block scoped. The difference between let and const being that we cannot modify a variable has been declared with const.