facebook google plus twitter
Webucator's Free Advanced JavaScript Programming Tutorial

Lesson: Scope

Welcome to our free Advanced JavaScript Programming tutorial. This tutorial is based on Webucator's Advanced JavaScript Programming course.

JavaScript variables can be scoped globally or locally to a function or block. Making sure that you correctly handle scope reduces the risk of unexpected side effects and bugs. The scope of the variable declaration determines the execution context of the variable; the ability to access a variable is determined by the execution context.

Lesson Goals

  • Learn about the scope of a variable.
  • Recognize how the var keyword affects scope./li>
  • Use the ES2015 let keyword to control the scope of a variable.
  • Use the ES2015 const keyword to control the scope and immutable binding of a variable.

Scope in JavaScript

In JavaScript, like in any programming language, "scope" refers to where a variable is accessible, based on where and how the variable was declared.

All variables have some scope: some portion of your script in which they can be referenced - printed to the screen, say, or used as part of a calculation. Global variables have, unsurprisingly, global scope - they can be accessed (set or read) anywhere in your script. Variables defined with the var keyword have function scope that is more limited; these are variables that might be accessed only within a function (often the function in which they are defined) or some other section of your script. Variables can also have block scope, as we'll see later in this lesson - specific to a block such as a for loop.

Variables declared outside of any function are global, meaning they are accessible to any code inside or outside of functions. Consider the following example:

Code Sample:

Scope/Demos/scope1.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>JavaScript Scope - Example 1</title>
	<script>
		var v1 = 13;

		function fun1() {
			document.write("inside function fun1, v1 has value: " + v1);
		}
	</script>
</head>
<body>
	<h1>Scope Example 1</h1>
	<p><em>This page has no errors</em></p>
	<script>
		document.write("outside of functions, v1 has value: " + v1);
		document.write("<hr>");
		fun1();
	</script>
</body>
</html>

Code Explanation

Variable v1, created on line 7, has global scope, and is thus accessible anywhere in our scripts. Variable v1 can be written to the screen on line 18 (via document.write) and can be accessed by function fun1 on line 10.

A variable created with the var keyword inside of a function has function scope, meaning that it is not accessible outside of that function. Let's look at a couple of examples - each generating an error - that demonstrate this fact:

Code Sample:

Scope/Demos/scope2.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>JavaScript Scope - Example 2</title>
	<script>
		var v1 = 21;

		function fun2() {
			var v2 = "variable 2";
		}

		function writeIt() {
			document.write('v1: ' + v1);
			document.write('<hr>');
			document.write('v2: ' + v2);
		}
	</script>
</head>
<body>
	<h1>Scope Example 2</h1>
	<p><em>This page generates an error</em></p>
	<script>
		writeIt();
	</script>
</body>
</html>

Code Explanation

Variable v1, created on line 7 in the same manner as in our first example, has global scope, and is thus accessible inside of function writeIt. Variable v2, however, is created inside of function fun2; as such, it is local to function fun2 and cannot be accessed by function writeIt on line 16. Opening the JavaScript console when browsing to this file shows the error "Uncaught ReferenceError: v2 is not defined".

To further illustrate the point, here's another example:

Code Sample:

Scope/Demos/scope3.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>JavaScript Scope - Example 3</title>
	<script>
		var v1 = 36;

		function fun3() {
			var v2 = "variable 2";
		}
	</script>
</head>
<body>
	<h1>Scope Example 3</h1>
	<p><em>This page generates an error</em></p>
	<script>
		document.write('v1: ' + v1);
		document.write('<hr>');
		fun3();
		document.write('v2: ' + v2);
	</script>
</body>
</html>

Code Explanation

Variable v1 again has global scope; we can successfully access it (to write to the screen) on line 18. But variable v2 is created inside of function fun3 - it's scope is local to function fun3. Thus we get an error (Uncaught ReferenceError: v2 is not defined") from line 21 when we try to write variable v2 to the screen, even though we call function fun3 before attempting to write the variable to the screen.

Note that, in all of the examples above, we created variables inside of functions using the var keyword.

The var Keyword

Perhaps more important than what happens when you declare a variable with the var keyword is what happens when you declare a variable without the var keyword. For variables declared without var outside of any function, there is no difference; as we've seen above, these variables are global in scope, accessible anywhere, inside or outside of functions.

But, for variables declared without var inside of functions, the situation is different: variables declared (by assigning a value) without using the var keyword are global, rather than local (i.e. local to their parent function), in scope. In short: any variable declared (by assigning it a value) without using the var keyword is global in scope, regardless of whether it is declared inside of a function or not. Let's look at an example:

Code Sample:

Scope/Demos/var1.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Var - Example 1</title>
	<script>
		var var1 = 51;
		var2 = 6;

		function setVar() {
			var3 = 46;
		}
	</script>
</head>
<body>
	<h1>Var Example 1</h1>
	<script>
		document.write("var1 has value: " + var1);
		document.write("<hr>");
		document.write("var2 has value: " + var2);
		document.write("<hr>");
		setVar();
		document.write("var3 has value: " + var3);
	</script>
</body>
</html>

Code Explanation

Variables v1 and v2 are both global in scope; the use (for var1) of the var keyword makes no difference here, since both variables are created outside of any function.

In function setVar we create variable var3 without using the var keyword - meaning that var3 is (despite being created inside of a function) global in scope. Thus we can write variable var3 to the screen on line 23 - we have to call function setVar first, but had we defined variable var3 with the var keyword on line 11 inside of the function then our attempt to write var3 to the screen on line 23 would have generated an "Uncaught ReferenceError" error.

Just to make sure this isn't already confusing enough, let's look at the case where we have two variables both created with the same name and with the var keyword, one inside of a function and one outside of a function.

Code Sample:

Scope/Demos/var2.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Var - Example 2</title>
	<script>
		var var1 = 18;
		var var2 = -18;
		function SetVar() {
			var var1 = 74;
			var2 = -74;
			document.write('Function SetVar called');
		}
	</script>
</head>
<body>
	<h1>Var Example 2</h1>
	<script>
		document.write("var1 has value: " + var1);
		document.write("<hr>");
		document.write("var2 has value: " + var2);
		document.write("<hr>");
		SetVar();
		document.write("<hr>");
		document.write("var1 has value: " + var1);
		document.write("<hr>");
		document.write("var2 has value: " + var2);
		document.write("<hr>");
	</script>
</body>
</html>

Code Explanation

We create variables var1 and var2, outside of (before) the function, and assign the values 18 and -18, respectively. Inside function SetVar, we declare and set the value of val1 to 74 using the var keyword. Then, we assign the value of -74 to the globally available variable var2 because we are not using the var keyword.

In the body of the page, we write variables var1 and var2 to the screen, call function SetVar, then write the variables to the screen again. Here's what we get:

output of var2.html

The result, while perhaps surprising, reflects the fact that the var keyword ensures that a variable created inside of a function is local to that function: the value of variable var1 is unchanged by the call to function SetVar, while var2 is changed by the call to function SetVar.

Because variable var1 is declared inside the function with the var keyword, its scope is local to the function - it is, in effect, a completely different variable than the variable created before the function on line 7. The variables happen to have the same name, but any modification to variable var1 inside function SetVar does not, as we see from the output of the page, affect the value of the variable declared on line 7.

Scope in Nested Functions

Variables created with the var keyword are, as we have seen, scoped to their enclosing function. A function defined inside of another function has access to variables created in the parent function. Here's an example:

Code Sample:

Scope/Demos/functionsinsidefunctions.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Scope: Functions inside Functions</title>
	<script>
		var v0 = 0;
		//accessible here: v0
		function fun1() {
			var v1 = 7;
			//accessible here: v0, v1
			function fun2() {
				var v2 = 26;
				//accessible here: v0, v1, v2
				function fun3() {
					var v3 = 234;
					//accessible here: v0, v1, v2, v3
				}
				fun3();
				//accessible here: v0, v1, v2
			}
			fun2();
			//accessible here: v0, v1
		}
	</script>
</head>
<body>
	<h1>Scope: Functions inside Functions</h1>
	<script>
		fun1();
		//accessible here: v0
	</script>
</body>
</html>

Code Explanation

We define a function fun1(), define function fun2() inside of fun1() and define function fun3() inside of fun2().

Variable v0, declared outside of any function, is global - we can access it anywhere. Because variable v1 is declared inside of function fun1(), v1 is accessible anywhere inside of function fun1() - including inside of functions fun2() and fun3() (which are declared inside of fun1()).

Variable v2 is accessible to functions fun2() and fun3() but not fun1(). Similarly, variable v3 is only accessible inside of fun3() but not to any of the "outer" functions.

Scope and nested anonymous nested functions offer some interesting possibilities in JavaScript, both for potential errors and for writing code (closures) that offers protection against unwanted errors. We'll dive deeply into closures later in this course; let's look now at some curious effects of execution context and functions - consider the following example:

Code Sample:

Scope/Demos/nestedanonymousfunction.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Nested Anonymous Function Example</title>
	<script>
		function createIDs(arr) {
		    var startingVal = 10;
		    for (var p=0; p<arr.length; p++) {
		      	arr[p]["UID"] = function() {
		      		return startingVal + p;
		      	}
		    }
		    return arr;
		}
	</script>
</head>
<body>
	<h1>Nested Anonymous Function Example</h1>
	<script>
		var people = [
						{fname:"Jane",lname:"Doe",UID:0},
					 	{fname:"Carly",lname:"Gutierrez",UID:0},
					 	{fname:"James",lname:"Chan",UID:0}
					 ];
		var peopleWithUniqueIDs = createIDs(people);
		for (var i=0; i<peopleWithUniqueIDs.length; i++) {
			document.write(peopleWithUniqueIDs[i]["fname"] + ' ' + peopleWithUniqueIDs[i]["fname"] + ", ID: " + peopleWithUniqueIDs[i].UID());
			document.write('<hr>');
		}
	</script>
</body>
</html>

Code Explanation

In our code, we create a JavaScript array of hashes people; each element of the array has fields fname ("first name"), lname ("last name"), and UID ("unique ID"); when created, each UID field is not unique, assigned a default value 0.

We create a function createIDs, whose job is to assign the unique IDs. We use what might at first appear to be a curious pattern here: iterating over the array (via a for loop), we assign each UID field to be the returned result of an anonymous function. The function ("anonymous" because it has no name) returns the sum of startingVal (given value 10 when initialized in function createIDs) and p, the for-loop counter variable.

We invoke function createIDs, passing array people to it, and store the result in array peopleWithUniqueIDs. When we iterate over the new array and write its values to the screen, we can call the anonymous function to get the calculated value of the UID field, with the code peopleWithUniqueIDs[i].UID(). The results may be surprising:

output from nested anonymous functions demo

We might expect that each UID field would get a unique value (10, 11, and 12) since the for loop variable takes on values 0, 1, and 2. But the anonymous function - that is, the function we call when we write the code peopleWithUniqueIDs[i].UID() - has access to the variables in function createIDs and, since that function has already run, the value of p (the for-loop counter variable) is 3.

The let Keyword

In JavaScript, as we have seen, a variable declared with the var keyword has function-level scope; the variable has global scope if created outside of any function. The let keyword works in a similar fashion but has block-level scope - with for loops and if statements being two common examples of enclosing blocks.

Keep in mind that the let keyword is a relatively-recent development in JavaScript - a new feature of ECMAScript6 (also known as "ES2015"). Like any ES2015 feature, we need to keep in mind that older browsers won't always implement the let keyword. The caniuse.com site is a great reference for checking browser support.

JavaScript precompilers, like CoffeeScript, allow you to write code using ES2015 standards and compile to ES5.

Let's look at a demonstration that compares when var and let are different and when they are the same:

Code Sample:

Scope/Demos/let.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Let</title>
	<script>
		var var1 = 'foo1';
		var let1 = 'bar1';

		function setVars() {
			var var2 = 'foo2';
			var let2 = 'bar2';
		}

		function forLoops() {
			for(var var3=0; var3<10; var3++) {
				//do something here
			}
			for(let let3=0; let3<10; let3++) {
				//do something here
			}
			
			/*
				this would generate an error for
				let3 but not for var3: var3 is scoped
				to the enclosing function (forLoops) but
				let3 is scoped to its enclosing block
				(the for loop)
			*/
			document.write("var3 has value: " + var3);
			document.write("<hr>");
			document.write("let3 has value: " + let3);
			document.write("<hr>");
		}
	</script>
</head>
<body>
	<h1>Let Example</h1>
	<p><em>This page generates errors</em></p>
	<script>
		/*
			this would work: both var1 and let 1 have global scope
		*/
		document.write("var1 has value: " + var1);
		document.write("<hr>");
		document.write("let1 has value: " + let1);
		document.write("<hr>");

		setVars();

		/*
			this would generate an error - but the same error for both:
			both var2 and let2 are scoped local to function setVars,
			and thus not accessible here
		*/
		document.write("var2 has value: " + var2);
		document.write("<hr>");
		document.write("let2 has value: " + let2);
		document.write("<hr>");

		forLoops();
	</script>
</body>
</html>

Code Explanation

We have three sets of variables here - var1, var2, and var3 (all created with the var keyword) - and let1, let2, and let3 (all declared with the let keyword).

The first set of variables - var1 and let1 - are both global in scope, because neither is created inside of a function nor a block. Thus neither generates an error when we access them to write them to the screen on lines 44 and 46.

var2 and let2 are both declared inside of function setVars() and have scope specific to that function; as such, we get an error when trying to access either variable. Lines 56 and 58 both generate the same "Uncaught ReferenceError" error when we try to write the variables to the screen. (Line 56 generates an error and execution halts so we don't actually get the second error from line 58, but the error would be the same for either.)

In function forLoops() we see the difference between var and let. Variable var3 is scoped to the function, and is thus accessible after the for loop in which it is declared; we can write it to the screen on line 30. Variable let3, however, is scoped to its enclosing block (the for loop) rather than to the function forLoops(); thus we get an error when trying to access let3 on line 32 to write it to the screen.

Most of the time, you would want to restrict a for loop variable to have scope specific to the loop, to avoid unwanted side effects after the running of the loop. As such, using let is generally preferable to var in this context - but keep in mind that, as mentioned above, let may not be supported by all browsers at the time of this writing.

The const Keyword

ES2015 also introduces the const keyword. While similar to let, in that the scope of a JavaScript variable declared with the const keyword has block-level scope, variables declared with const create an immutable binding. For example

const foo = 17;
foo = 29;

throws an error.

Variables declared as const create a read-only reference - a binding - to a value. This does not mean that the value can't be changed; rather, the variable identifier can't be reassigned. For primitive values (numbers, strings, etc.) this effectively means that you can't change the value. But if the content were an object, for example, the object itself could be altered - a property of the object added or an existing property changed. Let's look at an example.

Code Sample:

Scope/Demos/const.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Const</title>
</head>
<body>
	<h1>Const Example</h1>
	<p><em>This page generates an error if line 21 is uncommented</em></p>
	<script>
		let let1 = 'I am let';
		const const1 = 'I am const';

		document.write(let1);
		document.write('<hr>');
		document.write(const1);
		document.write('<hr>');

		let1 = 'I am changed let';
		//the following line throws an exception:
		//const1 = 'I am changed const';

		const obj1 = {};
		obj1.foo = 21;
		document.write(obj1.foo);
	</script>
</body>
</html>

Code Explanation

If we were to uncomment line 21:

const1 = 'I am changed const'

the page would throw an exception, since we cannot change the value of the binding of the variable: any variable declared as const with an assignment to a primitive value (numbers, strings, booleans, symbols, null, or undefined) is immutable.

The code on line 24, however, does not throw an error: since obj1 was declared as const and set to an empty object, we can assign properties to the object. Similarly, we could add values to a JavaScript array variable declared as const.

Scope

Duration: 10 to 15 minutes.

In this exercise, you will fix some incorrect code by handling scope.

  1. Open Scope/Exercises/scope.html for editing.
  2. The purpose of function writeIt is to write the first numTimes multiples of multiple. For instance, writeIt(3, 2) should write 0 2 4 to the screen, since the first three multiples of 2 are 0 (that is, 0 x 2), 2 (1 x 2), and 4 (2 x 2). Similarly, writeIt(4, 3) should write 0 3 6 9, since the first four multiples of 3 are 0, 3, 6, and 9.
  3. The code does not, however, work as intended. Find and fix the error.
  4. Test your solution in a browser.

Solution:

Scope/Solutions/scope.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Scope: Functions inside Functions</title>
	<script>
		function writeIt(numTimes, multiple) {
		    function doMultiplication(val, multiple) {
		        var i = val * multiple;
		        return i;
		    }

		    for (var i=0; i<numTimes; i++) {
		        document.write(doMultiplication(i, multiple) + ' ');
		    }
		}
	</script>
</head>
<body>
	<h1>Scope Exercise</h1>
	<script>
		writeIt(5, 2);
	</script>
</body>
</html>

Code Explanation

Perhaps unsurprisingly, the error is related to scope: function doMultiplication uses a variable i which, in the absence of any var or let keyword, modifies the i variable in the for loop. One way to fix this is to use the var keyword in the declaration of variable i in function doMultiplication - this scopes i local to function doMultiplication and thus it does not modify the value of i in the for loop from the calling code. (Of course, one could make the case that function doMultiplication is not needed here for this simple purpose, but we offer this exercise as an example of how easy it is to run into scope issues with variables and functions.)

Solution:

Scope/Solutions/scope2.html
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Scope: Functions inside Functions</title>
	<script>
		function writeIt(numTimes, multiple) {
		    function doMultiplication(val, multiple) {
		        i = val * multiple;
		        return i;
		    }

		    for (let i=0; i<numTimes; i++) {
		        document.write(doMultiplication(i, multiple) + ' ');
		    }
		}
	</script>
</head>
<body>
	<h1>Scope Exercise</h1>
	<script>
		writeIt(5, 2);
	</script>
</body>
</html>

Code Explanation

In this second solution, we declare the for loop variable i with the let keyword. This scopes i to the nearest block - in this case, the for loop. This ensures that - even if we incorrectly left off the var keyword when declaring a same-named variable in function DoMultiplication - other variables would not modify the for loop variable.