Page 2: Variables and Scope

JavaScript Variables

Javascript has 3 main ways to declare variables: let, const, and var.

var is the old way to declare variables. Don’t use it. It creates variables that object confusing rules. The lexical scoping rules discussed here do not apply. var is one of those legacies of the “bad old days” when JavaScript was a poorly designed language. Just don’t use var.

Technically, function canm also be used to declare variables. But think about

function foo(arg) { /* some code between the braces */ }
as equivalent to:
const foo = function (arg) { /* some code between the braces */ };
It’s not exactly the same - but the differences are too small to worry about for us.

Both const and let declare variables using the “modern rules.” They are what we should use in our JavaScript programs.

Lexical Block Scope

The Scope of a variable is the range of the program where the variable can be “seen”.

For const and let variables in JavaScript, the basic rules are fairly simple:

  1. Variables can only be seen after they have been declared.
  2. Variables can only be seen inside the block where they are declared. (a block is more or less a set of curly braces)

You are probably used to this. It is the way most other programming languages work. For example…

1
2
3
4
5
6
7
// this is before x is declared - and outside its block
if (true) {
    // this is before x is declared
    let x = 0;  // x is declared here
    // this is after x is declared - we can use it here
}
// this is outside of x's block - we can't use x here

Based on Javascript’s rules, we can use x on line 5, but not on 7, or 3, or 1. The scope of variable x is from where it is declared until the close brace (}) that ends the block it is declared in.

That might seem so simple that I shouldn’t have bothered to say it, but I want to be explicit about the notion of scope, because it can get more complicated. However, it isn’t too complicated: we can always figure out the cope of a variable by reading the program. The variable is only in scope between where it is declared and the close brace.

The complexity comes in because blocks nest - we can have blocks inside of blocks. Here’s another toy example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let a = 0;
function foo() {
    // variable a is in scope, b and c are not
    let b = 0;
    // a and b are in scope, c is not
    if (true) {
        // a and b are still in scope
        let c = 0;
        // now all 3 variables are in scope
    }
    // c is no longer in scope, only a and b are
}
// only a is in scope

This example is only slightly trickier. Notice that on line 3, we are in a new block, but a remains in scope. Similarly, on line 6 we start a new block, but the things that were in scope remain in scope. However, when we declare c in this inner block, its scope ends when the inner block ends (on line 10).

Of course, we can re-use variable names. So here’s a classic programming test question, what number does this program print?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let a = 0;

function f() {
    a = a+1;
}
function g() {
    let a = 0;
    if (true) {
        a = a + 2;
    }
    f();
}
g();
console.log(a);

You should be able to figure out that it prints 1. The variable a inside the f function is the one defined on line 4. The variable a on line 9 is the one defined on line 7. When g is called, it executes line 9 (setting the a variable on line 7 to the value 2), and it then calls the f function that changes the a variable on line 1.

Of course, this can be even trickier since we can define functions inside of functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function f() {
    let a = 0;
    function g() {
        let a = 1;
        a = a+1;
    }
    function h() {
        a = a+2;
    }
    g();
    h();
    return(a);
}
console.log( f() );
You should be able to figure out that this prints 2. The a on line 4 is in scope from lines 4 to 6. The a on line 8 comes from its enclosing block (the a on line 2). Similarly, the a on line 12 is the a from line 2.

These examples didn’t consider arguments. But basically, function arguments are the same as variables declared right at the beginning of the block.

Next, we’ll see how this leads to closures.

Next: Closure