Description
When can you access a JavaScript variable? It depends on its scope. In this lesson we discuss the various scopes a variable can occupy, including global vs local, functional vs lexical, private vs public.
Slides
Scope
scope = all the variables and functions that are visible from a given location in your code
The two primary forms of scope are Global and Local
Globally scoped variables can be seen from anywhere in the program
Locally scoped variables can be seen only nearby where they are defined -- usually inside the same function or code block
Global Scope
If you declare a variable without a keyword (var
, let
, const
) then it is a global variable and can be seen and used by any line of code in your entire program
Global variables are very useful but also very dangerous. A mistake in any part of your program using a global variable could introduce a bug in any other part of your program using that global variable.
(ES5 introduced strict mode which can reduce this risk -- though not eliminate it entirely)
Implicit vs. Explicit globals
Globals are usually bad, but they are good for when you want to call a particular function from literally anywhere in your code... for instance, when you want to tell your analytics server that something interesting just happened.
If you really want to use a global variable, you should do so explicitly, so other readers of your code will know that you did it intentionally.
JavaScript programs have a global object whose properties are available as global variables. In web browsers, the global object is named window
; in NodeJS, the global object is named global
.
// implicitly global
sendAnalytics = function(message) { ... }
// explicitly global (Browser)
window.sendAnalytics = function(message) { ... }
// explicitly global (NodeJS)
global.sendAnalytics = function(message) { ... }
Either of the above lines (in an HTML JS app) will allow any line in the entire rest of your program to call sendAnalytics('user clicked "unsubscribe" button')
Scope is a One-Way Mirror
scope is a one-way mirror -- inner scopes can see out, but outer scopes cannot see in
Mr. Bean -- in the interrogation room scope -- can't see the cops in the observation room scope.
Block Scope
let
and const
are block-scoped: any block of code surrounded by {
curly braces }
can have its own set of local let
variables
let name = 'Mr. Bean';
{
let name ='Detective Bob';
{
console.log(name);
}
console.log(name);
}
console.log(name);
If a variable name can't be found in the current scope, then JavaScript looks in the next outer scope, and so on
Exercise: Guess the Variable
- Which fruit would be logged below?
let fruit = 'Apple';
{
let fruit ='Blueberry';
{
let name = 'Cantaloupe';
}
console.log(name); // What is this fruit?
}
Top Level Functions are Global
A function defined with the term function
at the left margin is hoisted, meaning it
- is placed into the correct scope before any other code is executed
- can be called "before" it is defined (above in the source file)
let name = 'Alice'; // this name is global
let alpha = function() {
console.log(name); // alpha can see global var
beta(); // alpha can see global function named beta
}
// alpha() uses let so must be called after it is defined
alpha();
function beta() { // beta is hoisted!
let name = 'Bob'; // this name is local to beta
console.log(name); // prints "Bob"
}
console.log(name); // prints "Alice"
Parameters are local to their function
let opinion = 'i love cheese';
console.log(rant(opinion));
function rant(message) {
let loudMessage = message.toUpperCase() + '!!';
return loudMessage;
}
the above rant
function has two locally scoped variables:
- the local variable
loudMessage
- the parameter
message
Exercise: Guess the Variable with Functions
let poet = 'Robert Frost';
function famousPoem(poet) {
let poemAuthors = {
'Robert Frost': 'Stopping by Woods on a Snowy Evening',
'Walt Whitman': 'Leaves of Grass',
'undefined': 'The Lanyard' // Billy Collins
};
return poemAuthors[poet];
}
famousPoem('Walt Whitman'); // Which Poem?
famousPoem(poet); // Which Poem?
poet = 'Maya Angelou';
famousPoem(); // Which Poem?
Scope Error
- when you try to use a variable that is out of scope, you will get an error
function gamma() {
let x = "declared inside gamma";
console.log("Inside gamma: x is " + x);
}
console.log(x); // ReferenceError: x is not defined
Closure Scope
JavaScript also supports lexical scope (aka "closure scope" or "nested scope") which means that variables defined above the current function may also be visible...
function sing() { // outer function
let numberOfBottles = 99
function bottlesOfBeer() { // inner function
let message = '' + numberOfBottles
+ ' bottles of beer on the wall';
return message;
}
while (numberOfBottles > 0) {
console.log(bottlesOfBeer())
numberOfBottles -= 1
}
}
bottlesOfBeer
is enclosed within sing
, so it inherits sing
's scope
numberOfBottles
is visible inside both sing()
and bottlesOfBeer()
Nested Scopes
Every time you call a function, JS creates a new scope
that scope points to the current scope
and so on recursively
(and -- strangely enough -- variables that are defined inside a nested function are still alive after that function returns (?!?!?!) -- more on this at the very end of this lesson)
Why Nested Scopes? 1
- so callbacks can access local variables just like their neighboring code can
function countLetters(words) {
let letterCount = 0;
words.forEach(function(word) {
letterCount += word.length;
});
return letterCount;
}
total
is visible inside the inner (callback) function as well as the outer (countLetters
), so forEach
can behave like other loops
This doesn't work:
function addLetterCount(word) {
letterCount += word.length;
}
function countLetters(words) {
let letterCount = 0;
words.forEach(addLetterCount);
return letterCount;
}
...because addLetterCount
is not nested inside countLetters
Why Nested Scopes? 2
- nested functions, e.g. the following function accepts a two-dimensional array and prints each row
function printGrid(grid, delimiter) {
function printRow(row) {
console.log(row.join(delimiter));
}
let i = 0;
while (i < grid.length) {
printRow(grid[i]);
i = i + 1;
}
}
this is a contrived example, but the idea is that
- you don't have to pass
delimiter
in toprintRow
, making your code a bit cleaner - you can descriptively name chunks of code as inner functions without "polluting the global namespace"
Why Nested Scopes? 3
- private state encapsulation with IIFE's (this is very tricky; for more detail, see the encapsulation lesson)
let count = (function() {
let value = 0; // private variable
let increment = function() {
value = value + 1;
return value;
};
return increment;
})();
count() // returns 1
count() // returns 2
count() // returns 3
value // ReferenceError: value is not defined
- we now have a function that contains its own persistent state
- sometimes called a generator or an iterator
- this is weird since a normal function always returns the same value given the same input
- the private variable
value
is still alive after the IIFE returns 😲
Links
Outline
- Scope
- Global Scope
- Implicit vs. Explicit globals
- Scope is a One-Way Mirror
- Block Scope
- Exercise: Guess the Variable
- Top Level Functions are Global
- Parameters are local to their function
- Exercise: Guess the Variable with Functions
- Scope Error
- Closure Scope
- Nested Scopes
- Why Nested Scopes? 1
- Why Nested Scopes? 2
- Why Nested Scopes? 3
- Links