Variable Scope

This reading introduces you to the concepts of scope and extent in JavaScript and teaches how to use global variables. Some of this discussion is immensely practical: you will need to use it in realistic and useful programs, including some upcoming CS 204 assignments. However, some of it is very abstract and theoretical. This is because scope is important in programming languages.

If you're already familiar with global vs local variables, you may find this introduction elementary, but my understanding is that CS 111 mostly avoids discussing global variables, so for many of you, this will be new.

Scope

In computer science, the word scope is used to describe where a name (like a variable or a function name) has a particular meaning or reference. That's very abstract, so let's see some examples. We'll focus on variables, but we could extend the discussion to function names.

Local Variables

You are probably familiar with local variables, from CS 111 or other courses, but let's refresh our memories.

The following example is from the Wikipedia page on Scope, translated to JavaScript from their Python code. (Python, JavaScript and most other programming languages work exactly the same way with respect to this example.)

function square(n) {
   return n * n;
}

function sumOfSquares(n) {
    let total = 0;
    let i = 0;
    while( i <= n ) {
        total += square(i);
        i += 1;
    }
    return total;
}

The first function just returns the square of its argument, so square(2) evaluates to 4 and square(3) evaluates to 9. The second function squares of all numbers less than or equal to its argument, adds them up, and returns the sum. So sumOfSquares(3) evaluates to 02+12+22+32 or 0+1+4+9 or 14. Try it!

The purpose of the example is to discuss the variable n. Each function has a variable n, but those two names have nothing to do with each other! Either could be renamed within its own function independently of the other function. We say that these variables are local to their function and they are called local variables.

They are also described as having function scope, which means that the variable is meaningful within its own function, but not in any other function. In fact, even though sumOfSquares calls square,1 it's not possible for the square function to refer to the n variable in the sumOfSquares function. They are different variables and they simply live in different worlds. They happen to have the same name, but that's a coincidence (much like two human beings with the same name are still different people.)

Counters

Now, let's turn to a situation where local variables aren't up to the job.

Try clicking on these buttons. Their behavior is not complicated. Think about the thumbs-up and thumbs-down icons on YouTube videos, the "like" button on FaceBook, Yelp and a zillion other websites, etc.

Counter: 0

Conceptually, it's clear that there's a counter variable somewhere that can be incremented and decremented.

Coding the Behavior

How would you code this behavior in a web page? Clearly, there are a few event handlers that are triggered by clicking on a button. That's pretty straightforward:

<button id="incr">increment</button>
<button id="decr">decrement</button>

And some JavaScript code to add functions that are the event handlers:

$("#incr").click( incrCounter );
$("#decr").click( decrCounter );

Now, how do we code these two functions, incrCounter and decrCounter?

Failed Coding Attempt

Here's an attempt to code the two counter-modifying functions that doesn't work. (Let's ignore the updating of the page for now; that's not the issue here. Instead, the functions will return the new value, so we can test these functions in the JS console.)

function incrCounter() {
    let counter = 0;
    counter ++;
    return counter;
}

function decrCounter() {
    let counter = 0;
    counter --;
    return counter;
}

Here's that code in action:

Counter: 0

Try clicking on the buttons; you'll find that you only get 1 or -1.

Copy/paste the function definitions into the JS console and try them there. You'll also find that they only return 1 and -1.

It seems like both of our functions always start the counter at zero, which is indeed the case.

Thinking back to our square and sumOfSquares example, we also see that these two counter-modifying functions are using local variables named counter and that the two counter variables are different variables that happen to have the same name. Moreover, these functions initialize the counter to zero every time they run, so we can never make any progress.

A Solution

What we need is:

  • a single variable, that is
  • shared by the two functions, and is
  • initialized to zero once

Here's how that's done in JavaScript:

var counter = 0; // initialized when the page loads

function incrCounter() {
    counter ++;  // modify the shared counter variable
    return counter;
} 

function decrCounter() {
    counter --;  // modify the shared counter variable
    return counter;
} 

function showCounter() {
    alert("the counter is "+counter);
}

Copy/paste this code into the JS console and try them!

A few observations:

The following line of code is outside any function, and it means that the variable is declared and initialized when the page loads.

var counter = 0;

Because this is a global variable, we will declare it with var.

Notice that the functions refer to an already existing variable. They do not use let or var in front of it (which would make the variable local).

Global Variables

The counter variable in the last section lives outside the two functions. It's called a global variable, because it can be referenced by any function anywhere in the file. (Indeed, it can even be referenced by functions defined in other files.) It can be referenced from anywhere (more precisely, by any code that is a part of our program and loaded into this browser tab).

That's quite sweeping. Later, we'll learn how to limit scope more carefully, but for now, we'll just have two scopes:

  • local variables that exist only in one function, and
  • global variables that exist everywhere.

Furthermore, we can talk about the lifetime of the variable (sometimes called its extent).

  • local variables only exist during the execution of the function they are in. Once the function returns, the variable ceases to exist. This is extremely ephemeral.
  • global variables exist from the time the page loads until the time we close the browser tab. So, they essentially live forever.

The "forever" nature of global variables is very useful. In our counter example, it means we can count up and down for as long as this page is running in your browser. In general, that gives the program the ability to keep track of things over time, such as:

  • what is in each square of an web-based tic-tac-toe game
  • the count of wins and losses in the tic-tac-toe game
  • the name and high score of each player on the leader board
  • etc

Shared Globals and Local Variables

The example of the counter demonstrates global variables, but there aren't any local variables for contrast. So we can add that wrinkle here:

var counter = 0; // initialized when the page loads

function updateCounter() {
    $("#counter_elt").text(counter);
}

function incrCounter() {
    let before = counter;
    counter ++;  // modify the shared counter variable
    console.log('incr', before, counter);
    updateCounter();
    return counter;
} 

function decrCounter() {
    let before = counter;
    counter --;  // modify the shared counter variable
    console.log('decr', before, counter);
    updateCounter();
    return counter;
} 

function showCounter() {
    alert("the counter is "+counter);
}

// these add the functions as event handlers, when the page loads

$("#incr").click(incrCounter);
$("#decr").click(decrCounter);
$("#show").click(showCounter);

Both of the counter-modifying functions save the prior value and print the before and after values to the console. The prior value is in a local variable, since it doesn't need to be shared. Both functions named the variable before, but that's a coincidence, not a necessity.

You can see the example in action here: counter example 1. Feel free to "view source" on the page and look at the JS code.

Visualization

As we've learned, there are two kinds of scopes:

  • a single, global scope, and
  • lots of little function scopes, one for each function

If you imagine each scope as red box, our code for this last example can be pictured something like this:

counter scopes

Two of the smaller red boxes contain the before variable, but you can see that they are different variables and they live in different boxes. I've shown the value of the variable as undefined because the functions aren't running right now in this picture, so the variable and its value don't exist yet. The two variables are mutually invisble (neither can see the other). The counter variable, on the other hand, is in the global scope and visible everywhere.

Notice that I put the function names, like updateCounter, in the global scope. That's because they are globally visible. Both incrCounter and decrCounter call updateCounter, so it's necessary that updateCounter be global.

At the end of the code is a few lines that add these functions as event handlers:

$("#incr").click(incrCounter);
$("#decr").click(decrCounter);
$("#show").click(showCounter);

That code executes in the global scope and needs to refer to these three names as global variables. Indeed, they are global variables; they are just global variables that contain functions. We could have defined them like this:

var incrCounter = function () { ...};

We didn't do that because error messages are nicer when functions aren't anonymous.

Admonitions

Global variables have been a part of computer programming from the very beginning. History shows, though, that global variables have a downside, namely the connections between functions.

Go back to the square and sumOfSquares example. If both functions had used a global variable named n, they would have interfered with each other. Invoking square would have messed up the value of n in sumOfSquares and the code just doesn't work. Local variables are far better in situations where we want to create a variable and we don't want to intefere with other people's code (and we don't want their code to interfer with ours).

In general, you should prefer local variables over global variables. This is why CS 111 focusses on local variables.

You should only use global variables when you need:

  • two or more functions to share and refer to the same variable
  • the variable to persist longer than any function call

Later, we'll learn fancy techniques to have scope and extent that is bigger than local and smaller than global.

Summary

local variables:

  • exist only within their own function
  • are declared using let within the function
  • last only as long as the function is executing

global variables:

  • can be refered to from anywhere
  • are declared outside any function
  • last forever — from the time the browser opens our page until the page is closed

  1. and so the value of n in sumOfSquares is in a stack frame. Since n is there in the stack, it seems possible for square to access that stack location and find the value of n in its caller, but that is, in fact, impossible. So the value exists but is inaccessible