Scope II: Scopes and Scope Chain
Goals
- Describe the differences between
var
,let
andconst
and when to use each - Predict how variables will behave when multiple scopes are involved
- Understand how the scope chain is initialized and utilized to resolve variables
Vocab
Scope
- The place in which a variable can be accessed.Scope Chain
- A stack of currently accessible scopes, from the most immediate context to the global context.Global Scope
- A globally scoped variable can be accessed anywhere in the programLocal or Function Scope
- A locally scoped variable can only be accessed in the function where it was declaredBlock Scope
- A block scoped variable can only be accessed in the set of curly brackets where it was declared (only applies tolet
andconst
)
Scope
Now that we understand the order of execution a bit, we can dive deeper into the concept of scope. Scope is the place in which a variable or value can be accessed.
Warmup
Pre-ES6 variables often are described as having either global scope or local (function) scope.
- How would you describe the differences between a globally vs locally scoped variable?
- Looking at the below example, does our
makeNoise
function have access to thecowNoise
andcatNoise
variables? - What about outside of our function? Do we have access to
cowNoise
andcatNoise
here as well?
var cowNoise = 'moo';
function makeNoise() {
var catNoise = 'meow';
console.log('Cow Noise inside of Function: ', cowNoise);
console.log('Cat Noise inside of Function: ', catNoise);
}
makeNoise();
console.log('Cow Noise outside of Function: ', cowNoise);
console.log('Cat Noise outside of Function: ', catNoise);
Global, Functional, and Block Scope
We have several scopes available to us: global, local (also known as function), and block scope.
Global scope
In Your Notebook
Here is an example that uses globally scoped variables. What rules apply to them?
var one = "one";
let two = "two";
const three = "three";
showNumbers();
function showNumbers () {
console.log("In function: ", one, two, three);
if (one && two && three) {
console.log("In if block: ", one, two, three);
}
}
Global Scope Takeaways
- Global scope is the default.
- Everyone and everything has access to the global scope.
- Functions and variables in the global scope are “vulnerable” because they can be accessed by everything and potentially mutated (changed).
var
,let
, andconst
can be globally scoped.
Function scope
In Your Notebook
Here is an example that uses locally scoped variables. What rules apply to them?
function readWords() {
var greeting = "Hello, friend, ";
let question = "how are you? ";
const response = "I am fine."
if (true) {
console.log('Sentence in the if block: ', greeting, question, response);
}
console.log(greeting + question + response);
}
readWords();
// Am I able to access the variables here?
console.log(greeting + question + response);
Function/Local Scope Takeaways
- Variables declared in the function (using
var
,let
, orconst
) can only be accessed by the other code inside the function. - Because these variables are only used and accessible within the function, it is clearer for another developer to understand the impact of changing them and is less likely to result in issues when compared to globally scoped variables.
- The global scope cannot access function scope.
Block scope:
In Your Notebook
Here is an example that uses block scoped variables. What rules apply to them?
function readWords() {
var greeting = "Hello, friend, ";
let question = "how are you? ";
const response = "I am fine."
if (true) {
var greeting = "Sup dawg, ";
let question = "what's good?";
const response = "Nm."
console.log('Sentence in if block: ', greeting, question, response);
}
console.log(greeting + question + response);
}
readWords();
Block Scope Takeaways
- Variables declared in the block statement (
if
blocks,for
loops, etc) usinglet
orconst
can only be accessed by other code inside the block. - Variables declared in block statements using
var
will not be scoped within the block (as this is a special feature oflet
andconst
). Variables declared withvar
will “leak out” of the block to the block’s parent scope.
Scope & Scope Execution Practice
Parent vs. Child Scopes
Let’s look at another example and compare how scopes work between the parent and child.
Problem #1
Review the example below and answer the following questions:
const array = [5, 4, 3, 2, 1];
const secondNumber = array[1];
function getFirstNumber() {
const firstNumber = array[0];
return firstNumber;
}
function getSecondNumber() {
return secondNumber;
}
console.log('getFirstNumber(): ', getFirstNumber())
// console.log('getSecondNumber(): ', getSecondNumber())
// console.log('secondNumber: ', secondNumber);
// console.log('firstNumber: ', firstNumber);
- Run the
getFirstNumber
either in a Repl or your console. What happens & why? - Do the same for
getSecondNumber
and discuss similarly what happens & why. - Finally, log
secondNumber
andfirstNumber
. Note what happens when doing one vs the other. Why?
Review Note:
Parent scopes do not have access to child scopes BUT child scopes do have access to their parent scope.
Block Scoped Variable Practice
As we discussed earlier, variables declared with the keyword let
or const
will be block scoped if declared within a block. This means that they are scoped to the block statement (if
, for
…) in which they are declared. When you see {
and }
, those curly brackets are likely creating a scope, - as with function
, if
, and for
.
Problem #2
Run the following examples in your Repl/console:
// Example #1:
let message = 'You are doing great!';
if (message.length > 0) {
let message = 'I think you are amazing!';
console.log('Inside of conditional:', message);
}
console.log('Outside of conditional:', message);
// Example #2
function getIndex(searchQuery) {
const names = ["Nik", "Travis", "Hannah"];
for (let i = 0; i < names.length; i++) {
if (names[i] === searchQuery) {
console.log ('The index is: ', i);
break; //break just stops the for loop execution
}
}
return i;
}
console.log('getIndex(): ', getIndex("Hannah")); // What will happen?
- Run this code in a Repl. What happens?
- Be prepared to try to explain what is happening and why. Guesses are fine!
- Then replace
let
withvar
in Example #2 and note what happens!
In Your Notebook
- Describe “scope” in your own words.
- What are the similarities and differences between
var
,let
, andconst
? - What might be a metaphor or analogy for scope? Draw or diagram it out.
Scope Chain
Whenever a variable is used, the JavaScript interpreter traverses the scope chain
until it finds an entry for that variable. Traversal on the scope chain always starts in the most immediate scope and moves towards the global space. Note that the scope chain is initialized during the “creation phase” of the interpreter running through the code. Let’s see an example of this in action!
In Breakout Groups
Consider the following example and explain what is happening:
let number = 10;
function logNumber() {
number = 20;
console.log('A', number);
}
console.log('B', number);
logNumber()
console.log('C', number);
- Before running the code, what do you think the value of
number
is in each of the logs? - Now run it and take note of what happens. Allow each person in the group to explain what they think is happening.
What is happening here?
logNumber
and its definition as well as the declarationnumber
are stored in global memory- Line 1 -
number
is assigned the value of 10 - Line 8 - prints
B 10
to the console - Line 10 -
logNumber
is invoked, creating a new execution context - Line 4 - A variable is declared without the keyword
var
and assigned a value. The interpreter searches in the current execution context to see where this variable was defined. Because it doesn’t find it declared in the current scope, it looks up the scope chain to the parent scope, which happens to be the global scope. The interpreter understands that this is to be treated as a re-assignment and assigned the value ofnumber
to 20, both locally and globally. - Line 5 - prints
A 20
to the console - Line 12 - prints
C 20
to the console
Major Takeaways
-
The scope chain (e.g. “What is the parent scope for this variable? The grandparent scope?”) is determined by where functions are defined in the code base…. not where functions are invoked.
-
Every time a variable is initialized, the interpreter will first look in its own scope to see if the label can be found. If it is not found, it will look “up” the scope chain to the parent scope to try to resolve the variable in the parent context.
-
If that label is never found, the interpreter will declare it globally on the window and the variable will be scoped as such.
Clarification between Scope Chain & Hoisting
It is important to note that the interpreter moving up the scope chain to resolve variable values is NOT hoisting. Remember that the JS interpreter hoists declarations (storing them in memory) during the creation phase, not when the code itself is being executed.
More Examples
With a partner, take turns walking through the following code examples:
Example 1:
function logLocalNumber () {
var localNumber = 20;
number = localNumber;
}
logLocalNumber()
console.log(number); // what will log here?
Example 2:
var givenName = 'Amon Williams';
function printGreeting() {
console.log(`Hello ${givenName}`);
}
printGreeting('Khalid') // what will log here?
printGreeting() // what will log here?
Example 3:
var givenName = 'Amon Williams';
function printGreeting() {
let givenName = "Khalid"
console.log(`A: Hello ${givenName}`);
if( givenName.split(" ").length < 2) {
givenName = "Khalid Williams";
console.log(`B: Hello ${givenName}`);
}
console.log(`C: Hello ${givenName}`);
}
printGreeting()
console.log(`D: Hello ${givenName}`);
// What logs at each letter?
Final Reflections
Using your journal, take a few minutes to answer the following:
- Why is it important to understand scope?
- What is the scope chain? What does it do?