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.
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:
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:
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
-
and so the value of
n
insumOfSquares
is in a stack frame. Sincen
is there in the stack, it seems possible forsquare
to access that stack location and find the value ofn
in its caller, but that is, in fact, impossible. So the value exists but is inaccessible. ↩