Scope
If you took Intro to Javascript, you learned about _block _scope vs. _function _scope. These determine where a variable can be seen in some code. Computer scientists call this lexical scope.
However, there also exists _another _kind of scope called runtime scope. When a function is run, it creates a new runtime scope. This scope represents the _context _of the function, or more specifically, the set of variables available for the function to use.
So what exactly _does _a function have access to?
Scope
A function's runtime scope describes the variables available for use inside a given function. The code inside a function has access to:
- The function's arguments.
- Local variables declared within the function.
- Variables from its parent function's scope.
- Global variables.
Check out the following image that highlights a function's scope, then we'll take a look at a live example.
Here's the code from the preceding video.
Video Recap
In the previous video, theintroduceMyself()
function contains a nestedintroduce()
function. Whileintroduce()
does not take in any arguments, nor are there any local variables declared within it -- variables in both the aforementioned settings are indeed inintroduce()
's scope.
introduce()
_does _use the global variablemyName
, however, as well as theyou
variable contained in its parent function,introduceMyself()
(whereintroduce()
was defined). Both are highlighted below:
const myName = 'Andrew';
// Global variable
function introduceMyself() {
const you = 'student';
// Variable declared where introduce() is defined
// (i.e., within introduce()'s parent function, introduceMyself())
function introduce() {
console.log(`Hello, ${you}, I'm ${myName}!`);
}
return introduce();
}
QUESTION 1 OF 4
Consider the following:
const num1 = 5;
function functionOne() {
const num2 = 10;
function functionTwo(num3) {
const num4 = 35;
return num1 + num2 + num3 + num4;
}
return functionTwo(0);
}
Which variables doesfunctionTwo()
have access to? Select all that apply:
num1
num2
num3
num4
It is
indeterminate.
SUBMIT: All four variables are available forfunctionTwo()
to use. Let's break this one down:
num1
is a global variable, accessible anywhere in the application code.
num2
is a local variable infunctionOne()
, the parent function in whichfunctionTwo()
is defined.
num3
is an argument passed directly tofunctionTwo()
.
num4
is a local variable infunctionTwo()
.
JavaScript is Function-Scoped
You may be wondering why scope is so heavily associated with_functions_in JavaScript. Especially if you've had past experience in another programming language, this might seem a bit unusual (e.g.,_blocks_in Ruby have their own scope)!
This is all because variables in JavaScript are traditionally defined in the scope of afunction, rather than in the scope of ablock. Since entering a function will change scope, any variables defined inside that function arenot_available outside of that function. On the other hand, if there are any variables defined inside a_block(e.g., within anif
statement), those variables_are_available outside of that block.
Let's see an example of how function-scoping in JavaScript works:
var globalNumber = 5;
function globalIncrementer() {
const localNumber = 10;
globalNumber += 1;
return globalNumber;
}
In the example above,globalNumber
is outside the function; it is a global variable that theglobalIncrementer()
function has access to.globalIncrementer()
simply has a local variable (localNumber
) declared within it, then incrementsglobalNumber
by 1 before returning the updated value ofglobalNumber
itself.
After calling the function a few times, we see that the value ofglobalNumber
has indeed increased each time:
console.log(globalIncrementer());
// 6
console.log(globalIncrementer());
// 7
console.log(globalIncrementer());
// 8
However, when attempting to accesslocalNumber
outside of the function, we see a error:
console.log(localNumber);
// ReferenceError: localNumber is not defined
Because JavaScript is function-scoped, functions have access to all its own variables as well as all the global variables outside of it.
💡 Block-Scoping 💡
ES6 syntax allows for additional scope while declaring variables with the
let
andconst
keywords. These keywords are used to declare_block-scoped_variables in JavaScript, and largely replace the need forvar
.We've used them throughout this course, but for a closer look, check out our course:ES6 - JavaScript Improved. Via MDN:
Scope Chain
Whenever your code attempts to access a variable during a function call, the JavaScript interpreter will always start off by looking within its own local variables. If the variable isn't found, the search will continue looking up what is called thescope chain. Let's take a look at an example:
function one() {
two();
function two() {
three();
function three() {
// function three's code here
}
}
}
one();
In the above example, whenone()
is called, all the other nested functions will be called as well (all the way tothree()
).
You can visualize the scope chain moving outwards starting at the innermost level: fromthree()
, totwo()
, toone()
, and finally towindow
(i.e., the global/window object). This way, the functionthree()
will not only have access to the variables and functions "above" it (i.e., those oftwo()
andone()
) --three()
will also have access to any global variables defined outsideone()
.
Let's now revisit the image from the beginning of this section, and visualize the entire process:
💡 The Global (
window
) Object💡Recall that when JavaScript applications run inside a host environment (e.g., a browser), the host provides a
window
object, otherwise known as theglobal object. Any global variables declared are accessed as_properties_of this object, which represents the outermost level of the scope chain.For a refresher, feel free to check outBeware of Globalsin Lesson 1.
Variable Shadowing
What happens when you create a variable with the_same name_as another variable somewhere in the scope chain?
JavaScript won't throw an error or otherwise prevent you from creating that extra variable. In fact, the variable with local scope will just temporarily "shadow" the variable in the outer scope. This is calledvariable shadowing. Consider the following example:
const symbol = '¥';
function displayPrice(price) {
const symbol = '$';
console.log(symbol + price);
}
displayPrice('80');
// '$80'
In the above snippet, note thatsymbol
is declared in two places:
- Outside the
displayPrice()
function, as a _global _variable. - Inside the
displayPrice()
function, as a _local _variable.
After invokingdisplayPrice()
and passing it an argument of'80'
, the function outputs'$80'
to the console.
How does the JavaScript interpreter know which value ofsymbol
to use? Well, since the variable pointing to'$'
is declared inside a function (i.e., the "inner" scope), it will override any variables of the same name that belong in an outer scope -- such as the global variable pointing to'¥''
. As a result,'$80'
is displayed rather than'¥80'
.
All in all, if there are any naming overlaps between variables in different contexts, they are all resolved by moving through the scope chain from inner to outer scopes (i.e., local all the way to global). This way, any local variables that have the same name take precedence over those with a wider scope.
QUESTION 2 OF 4
What will the console display whenmyFunction()
is called?
let n = 2;
function myFunction() {
let n = 8;
console.log(n);
}
myFunction();
// ???
2
8
null
undefined
SUBMIT: When attempting to access a variable, the JavaScript engine look outwards, starting from the innermost level. In this example, since the JavaScript engine finds n
declared as a local variable within myFuction()
, it will never reach the n
variable (whose value is 2
) further up the chain. As such, 8
is logged to the console.
QUESTION 3 OF 4
When searching for variables along the scope chain, in what order will the JavaScript interpreter search?
ORDER LOCATION
1st: Local variables
2nd: Parent function's variables
3rd: Parent function's _parent function's _variables
4th: Global variables
SUBMIT: The JavaScript engine will look at the innermost level and move outward -- from local variables defined directly in a function, all the way up the the variables in the global scope (if needed).
QUESTION 4 OF 4
When the following code runs, what is the output of the first, second, and third logs to the console (respectively)?
let n = 8;
function functionOne() {
let n = 9;
function functionTwo() {
let n = 10;
console.log(n); // First log
}
functionTwo();
console.log(n); // Second log
}
functionOne();
console.log(n); // Third log
8
,9
,10
10
,9
,9
9
,10
,8
10
,9
,8
It is indeterminant.
SUBMIT: The code outputs10
,9
, and8
to the console (in that particular order). With each log to the console, the JavaScript engine will search for the innermost (i.e., most local) variable it can find; this is the value passed into theconsole.log(n)
expression.
WhenfunctionTwo()
is called, even though there's ann
variable that has a value of9
, and anothern
variable that has a value of8
-- the JavaScript engine won't find them! Since there's another variablen
that is more local (i.e., directly insidefunctionTwo()
), the JavaScript engine will use_that_variable instead.
Summary
When a function is run, it creates its own scope. A function's scope is the set of variables available for use within that function. The scope of a function includes:
- The function's arguments.
- Local variables declared within the function.
- Variables from its parent function's scope.
- Global variables.
Variables in JavaScript are also function-scoped. This means that any variables defined inside a function are not available for use outside the function, though any variables defined within blocks (e.g.if
orfor
) _are _available outside that block.
When it comes to accessing variables, the JavaScript engine will traverse the scope chain, first looking at the innermost level (e.g., a function's local variables), then to outer scopes, eventually reaching the global scope if necessary.
In this section, we've seen quite a few examples of a nested function being able to access variables declared in its parent function's scope (i.e., in the scope in which that function was nested). These functions, combined with the lexical environment it which it was declared, actually have a very particular name: closure. Closures are _very _closely related to scope in JavaScript, and lead to some powerful and useful applications. We'll take a look at closures in detail next!
Further Research
- Intro to JavaScript (Lesson 5's coverage ofscope)
- Douglas Crockford's discussion of block-scoped variables in The Better Parts
- Functions and Function Scope on MDN