Scope
Enjoy these notes? Want to Slam Dunk JavaScript?
These are notes based on my Beginner JavaScript Video Course. It's a fun, exercise heavy approach to learning Modern JavaScript from scratch.
Use the code BEGINNERJS for an extra $10 off.
We have touched on scope a few times throughout the course already because it's impossible to talk about variables without touching on scope.
What is scope? Scope answers the question "where are my variables and functions available to me?".
So far in the course, we have been making functions and variables and they have generally been available anywhere we wanted them. We have also seen some issues like when you create a variable inside of a function, and they're not available outside the function.
That is what scope is.
Go to the playground folder and create a new file called scope.html
, and use your html base inside of it.
Add a script src to a file called scope.js
which will live in the same directory.
<script src="./scope.js"></script>
Add a new file scope.js
in /playground
and add a console.log('It works!');
.
Open the file up in the browser to make sure it works.
Global Variables
We will start by talking about the first type of scope which are referred to as global variables.
With global variables, whether you have a script tag, or javascript file, or run it in the console, anytime you declare a variable, it will be available anywhere in the application.
For example, if you add in scope.js
the following code const first = 'wes';
and then in the console type first
, it will return the value of first.
Also, if you were to add another script tag in scope.html
like below 👇, will it work?
<script src="./scope.js"></script>
<script>
console.log(first);
</script>
That does work, the console will log "wes".
Why does it work?
Because when you just go ahead and create a variable in a javascript file, not inside of a function, not inside of a module, not inside of an if statement, when you create it out in the open, that is what is referred to as a global variable.
You can access global variables from any other javascript that is running on the page like a script tag or via the console.
In the browser, the global scope is called the window.
If you type window
in the console, you will see every single thing that is attached to the global scope is inside of the window.
That is why sometimes you will see people doing things like window.setTimeout()
. There is also just regular, old, setTimeout()
, both are the exact same thing.
Methods that are globally available to us like setTimeout()
are actually on the window object, and that is what is called global.
There is one thing to know about const
and let
.
Let's say we added this code to scope.js
.
/* eslint-disable */
const first = 'wes';
let second = 'bos';
var age = 100;
If you reload scope.html
in the browser and then in the console type in first
, second
and age
, you will see the following values returned because they are all global variables 👇
However, if you type in window.first
and window.second
you will get undefined
but when you type in window.age
it will return 100
.
This is because var
variables are attached to the window
object and they are globally scoped, and const
and let
variables when declared like the example above are still globally scoped, they are just not attached to the window.
That is not really all that important because you shouldn't be making global variables anyway. This was just to demonstrate why sometimes people write window.
or just type the global variable directly.
The same thing goes with functions.
If we make a function function sayHi(){console.log('hi!');}
, we can call it by the following methods:
<script src="./scope.js"></script>
<script>
sayHi();
</script>
- typing
sayHi()
in the console - typing
window.sayHi()
in the console
Why is it available at window.sayHi()
?
Because anything that is in the global scope is attached to the window object with the exception of const
and let
variables.
Using global scope is almost never a good idea, but before we learn why that is, we need to learn about how different parts of scope work.
Function Scoping
Let's get rid of the <script>sayHi();</script>
code in scope.html
.
Remove everything within scope.js
and then add
const age = 100;
Create a function go()
, and the following logs 👇
const age = 100;
function go() {
const hair = 'blonde';
}
console.log(age);
console.log(hair);
- When we
console.log(age)
, is it going to be 100? - When we
console.log('hair')
, will it be blonde?
Age returns 100, but hair throws a reference error.
Maybe that is because we did not run the go()
function, we just defined it?
Let's try running the go function so that variable gets declared. Before console.log(age);
add go();
.
If you refresh the page and look at the console, we will still see the error. Why?
This is the next type of scope which is called function scope.
What function scope is, is when variables are created inside of a function, those variables are only ever available inside of that function.
That means that anything we create inside of the go()
function, is not available outside of that function, unless we were to explicitly return it and put it into its own variable when that function is run.
If you modify the console.log(hair)
to be within the go()
function, it will log. 👇
function go() {
const hair = 'blonde';
console.log(hair);
}
You can think of the curly brackets in functions as gates.
They keep the variables in and do not allow them to leak out.
Now is that the same for the opposite?
If we were to console.log(age)
from within the go()
function, would that work?
If variables can't leak out, can they go in?
If you refresh the page and look at the console, you will see it worked.
This is an interesting thing about javascript and most programming languages.
Variables, if they are not found inside of a function, will go up a level higher and look for a variable in that scope. If it's not available in that scope, it will go up a level higher. (We will talk about multiple nested scope in just a second).
So what is happening is inside the go()
function the code is saying, "there is no variable inside of this function called age, so I will go up one more level and look up".
Now if we added as the first line of the go()
function, const age = 200;
and refresh the page, is that going to error?
Is that going to be 200 or 100?
It will be 200.
Two things:
- is that allowed?
- why it is 200?
It's 200 because it looks in the local function first and it does find a variable called age.
However, if it is not found, it will go one level higher and look for it at that level.
Why is this allowed? Why is it not breaking? If you hover over the variable in VSCode, you will see an error in ESLint that says
'age' is already declared in the upper scope.
That is what is referred to as shadow variables.
You can name variables the same thing, if they are not in the same scope.
However, it's not a good idea because if you name a variable something that is the same as in another scope, you limit yourself to being able to access that.
const age = 100;
function go() {
const age = 200;
const hair = 'blonde';
console.log(age);
console.log(hair);
}
In the example above 👆, if we wanted to access the age = 100;
from the go()
function, there is no way to do that because the variable has been shadowed (meaning it has been over-written).
A tip is that if you ever have a variable inside of a function that is very similar to a variable outside of a function, name the variable inside of the function more specifically so you have access to both.
In this case, inside of the function you could declare a variable like const myAge = 200;
instead of just calling it age
.
Block Scoping
The next type of scope is block scope.
We will start by talking about var
, let
, and const
variables and how they are scoped differently.
const
variables cannot be reassigned, and let
and var
variables can be re-assigned. They have a different way of how they are scoped.
Let's do an example.
Will the code below work 👇?
if (1 === 1) {
const cool = true;
}
console.log(cool);
No, it throws the error shown above 👆
Uncaught ReferenceError: cool is not defined
What if we make it a var
variable instead?
if (1 === 1) {
var cool = true;
}
console.log(cool);
It works!
What if we make it a let
variable?
if (1 === 1) {
let cool = true;
}
console.log(cool);
It doesn't work, we get the same error.
What is happening there is whenever you have a set of curly brackets like in the if statement above, that is what is referred to as a block. (Blocks will be showing up in most places, the most common is probably an if statement. We will have lots more examples throughout the course.)
You can think of those brackets as gates. Those gates keep variables in.
Anytime something is created inside of these gates, they are not allowed to leave the gates. If you need to access a variable outside of the gates that was originally created inside of the gates, you are not allowed to do so.
There are a few solutions to that because every now and then you run into a situation where you do need a variable to be available outside of a block.
What happens is you have to create the variable above it, as we will demonstrate in this example 👇
let cool;
if (1 === 1) {
cool = true;
}
console.log(cool);
In that example, we have declared a variable cool
without assigning anything to it.
On the line that previously had let cool = true;
, we do not declare the cool variable, we simply update it with cool = true
.
What that does is it creates the variable in the higher scope (in this example it is a global variable but if this was created inside of a function it would be function scoped).
Then, we update the variable, and then it's available to us outside of the function.
Here is another example that is a more realistic, where we are getting into two different kinds of scopes 👇
function isCool(name) {
let cool;
if(name === 'wes') {
cool = true;
}
console.log(cool);
return cool;
}
So what is going on here is we have one scope, which is the function scope (function isCool(name){...}
).
One thing that can get confusing is that a function also has a block, so any let
, var
or const
variables are automatically scoped to a function, because regardless, if its a function or a block, it's a set of curly brackets so it's scoped to that.
And then we have block scope which is within the if (name === 'wes') {...}
.
Again to demonstrate, if we changed the cool
variable within the if statement to a const variable, like in this example, it would not work 👇
function isCool(name) {
let cool;
if (name === 'wes') {
const cool = true;
}
console.log(cool);
return cool;
}
However, the example below does work 👇
function isCool(name) {
let cool;
if (name === 'wes') {
cool = true;
}
console.log(cool);
return cool;
}
Although we went over how to access variables that are block scoped, in most cases that is not something you need to get fixed, and it is actually really nice to have block scope because you don't have variables leaking out of it.
In the past, we have had for loops such as the following:
for (var i = 0; i < 10; i++) {
console.log(i);
}
That will log 0 - 9 as shown below 👇
If you type i
in the console, it will return 10.
We have this random variable that is out floating called i
, which should have been contained within the for loops but because it's a var
, it has leaked outside.
By simply making it a let
variable, that random i
variable will no longer be floating because it will be scoped to the curly brackets.
Block scoping is one of the reasons people say use const
by default, let
when you want to re-assign it and don't use var
unless there is a specific use case for it.
To go back to our example earlier with isCool()
, we could have also solved it by changing the cool
variable to a var
as shown below 👇
function isCool(name) {
if (name === 'wes') {
var cool = true;
}
console.log(cool);
return cool;
}
Why does that work?
Because it's a var
variable.
var
variables are not block scoped, they are function scoped.
That means they are available outside of the if statement block, but they are only available inside of the isCool()
function.
We will do another example of scope look-up to solidify that concept. 👇
const dog = 'snickers';
function logDog() {
console.log(dog);
}
function go() {
const dog = 'sunny';
logDog();
}
go();
What is going on here?
- We have a variable
dog
which is set to"snickers"
. - We have a function
logDog
that will log thedog
variable. - We also have a
go()
function that creates a variable nameddog
again, and sets it to the value of"sunny"
. - Then we run that
logDog()
function, and then we rungo();
.
When we call the logDog()
function, is it going to log Sunny or Snickers?
You may think it will log Sunny because we declare the dog in the function scope, so when the function runs, shouldn't it look in it's own scope for that?
Or you might think it may log Snickers because the function is declared within a scope, having dog
variable inside of it so it will look up for that variable.
What does it log? Snickers!
Even though we ran the logDog()
function within the go()
function and that function has a locally scoped variable set to the value of Sunny, it's still logging Snickers.
Lexical and Static Scoping
That is because javascript has what is referred to as lexical scoping or static scoping (those are buzz words in javascript).
What that means is the way that variable look-up or scope look-up happens, is where the functions are defined, not where they are run.
So even though the logDog()
function is run inside of another function which has a locally scoped dog
variable, it doesn't care about where it's run, it cares about where it is defined.
Because logDog()
is defined where it is, since it doesn't have a local variable named dog
, it will just go up one level.
Ideally the function logDog()
would take a parameter of dog
and then log whatever parameter value it is passed like so 👇
const dog = 'snickers';
function logDog(dog) {
console.log(dog);
}
function go() {
const dog = 'sunny';
logDog('Rufus');
}
go();
Now that console will log "Rufus". Why?
Because when a function takes in an argument, it will make local variables inside of that function named whatever you named the parameter, and then that is available to them.
It is the same as doing the following 👇
function logDog(dog){
const dog = whateverYouPassedInAsTheFirstArgumentToTheFunction;
console.log(dog)
}
That is what javascript is essentially doing behind the scenes. It is taking the value that you pass the first parameter and making a local variable for the function.
function go() {
const dog = 'sunny';
logDog(dog);
}
The code doesn't care what you called the variable that you are passing in, it just cares about the value.
The example above 👆 is the same thing as just passing in a string of Sunny, as shown below 👇
function go() {
const dog = 'sunny';
logDog('sunny');
}
We will go over best practices and many more examples in the course.
Best Practices
- Try not to create global variables
- it is fine for us to create them while doing these examples and when we get into modules, it is almost impossible to create global variables unless you explicitly do something like
window.IAMGlobal = 'wes'
. - Unless you explicitly attach it to the window, with a module, it is very hard to create a global variable which is intentional.
- Functions are scoped the exact same as variables
- You can create functions inside of other functions. We will look at examples of why you might want to do that later.
Here is an example in the meantime 👇
function sayHi(name) {
function yell() {
console.log(name.toUpperCase());
}
yell();
}
If you were to write sayHi('wes')
in the console, it will return WES.
However if you try to call the yell
function outside of sayHi()
, as shown below, it will throw an error 👇
function sayHi(name) {
function yell() {
console.log(name.toUpperCase());
}
yell();
}
yell();
What is happening there is just like variables, functions are scoped to the parent function.
If you create a function inside of another function, that function will only ever be available inside of that.
That is starting to open up what is referred to as a closure, which is a more advanced example we will go over in the future.
It is possible to do a function inside of a function, and we will learn that in future lessons.
Find an issue with this post? Think you could clarify, update or add something?
All my posts are available to edit on Github. Any fix, little or small, is appreciated!