Scope and Context¶
- When we talk about scope we are talking about the variables a piece of code has access to at runtime.
- When we talk about context we are talking about the value of
this
and all of it's properties.
var
is bananas¶
Variables defined in the default or root scope are accessible globally, and variables defined within functions are only accessible within that function.
var a = 'apples'
function fruitVendor() {
var b = 'bananas'
console.log('We are inside the scope of the fruitVendor function:')
console.log('> a is for ' + a)
console.log('> b is for ' + b)
console.log('\n')
}
fruitVendor()
console.log('We are at the default or root scope level:')
console.log('> a is for ' + a)
console.log('> b is for ' + b)
We get scope conflict when there is a variable of the same name in the parent and child scope.
var a = 'apples'
function fruitVendor() {
var a = 'avocados'
console.log('We are inside the scope of the fruitVendor function:')
console.log('> a is for ' + a)
console.log('\n')
}
fruitVendor()
console.log('We are at the default or root scope level:')
console.log('> a is for ' + a)
Although the code runs, the problem is that we no longer have access to the parent variable of the same name due to naming conflicts.
This is not really new information. But we are going somewhere with this which is JS specific so bear with me.
Above, we looked at function scope, but what about block scope? Is there much difference here when we try to define variables inside if
, for
, or while
blocks of code?
if (true) {
var c = 'clementine'
}
console.log('> c is for ' + c)
var
values can be accessed outside of the scope of the block in which they were defined. This is considered by some to be inconsistent and confusing, particularly because you might feel that scope could be imagined as being encapsulated in curly braces. Lack of block scope isn't something you'd be used to if coming from many other programming languages. So in ES6 a new pair of definitions were introduced....
let
and const
¶
let d = 'durians'
if (true) {
let e = 'elderberries'
console.log('We are inside the scope of the if block:')
console.log('> d is for ' + d)
console.log('> e is for ' + e)
console.log('\n')
}
console.log('We are at the default or root scope level:')
console.log('> d is for ' + d)
console.log('> e is for ' + e)
Now block scope is operating the same as function scope and we can all sleep soundly at night. Hooray.
Getting Loopy¶
So we know that let
and var
operate differently in terms of scope. What happens if we use var
when adding lazy loaded functions to an array, executing them at some later time?
var toBeExecuted = []
for (var i=0; i<3; i++) {
toBeExecuted.push(() => console.log(i))
}
toBeExecuted.forEach(lazyFn => lazyFn())
What happened? Instead of creating a local variable i
for each increment in the loop, it ended up printing the final value for that variable for all function calls.
It feels unexpected and although there are work-arounds for still using a var
they can be a little verbose.
Enter the let
keyword:
let letItBeExecuted = []
for (let i=0; i<3; i++) {
letItBeExecuted.push(() => console.log(i))
}
letItBeExecuted.forEach(lazyFn => lazyFn())
Magic ✨
A Warning¶
It's important to note that JS still compiles when you create a new variable, even if you don't specify var
, const
, or let
. When you do so, it first searches the current scope for a variable of that name, then trickles up through parents layers.
⚠️ If it doesn't find it all the way up to the root, it will create the variable there in the root layer, also known as a global variable. This is known as "polluting the global scope".
To avoid this place the string "use strict"
at the top of the entry point file, causing a ReferenceError
message if this is attempted. This trick solves a whole host of other problems as well.
"use strict"
function badFunction() {
w = 'watermelon'
}
console.log(w)
Let's stop here for now. Next time we will dive deeper into what lexical scope really means. We'll get into closures and hoisting, and even explore the differece between dynamic and static scoping to really understand what we're working with.
'til next time 👋