Scope

Beginner JavaScript

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.

BeginnerJavaScript.com

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 taking 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.

console showing Window object

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 πŸ‘‡

console showing value of the typed variable within scope

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.

console showing undefined for variables that was not declared with var

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>
  1. typing sayHi() in the console
  2. 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);
  1. When we console.log(age), is it going to be 100?
  2. When we console.log('hair'), will it be blonde?

console showing logged value for defined variable and throws a reference error for undefined variable

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 it's 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:

  1. is that allowed?
  2. 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.

Editor showing a warning that 'age' is already declared

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);

console showing Uncaught ReferenceError: cool is not defined

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!

console showing logged value as true

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.

console showing Uncaught ReferenceError: cool is not defined

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 are gates. Those gates keep variables in.

Anytime something that was created inside of these gates, they are not allowed to leave the gates. If you need to access a variable that was created inside of the gates, outside of the gates, you are not allowed to do it.

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 it creates it's 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 πŸ‘‡

console showing logged numbers from 0 to 9

If you type i in the console, it will return 10.

console showing i as 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?

  1. We have a variable dog which is set to "snickers".
  2. We have a function logDog that will log the dog variable.
  3. We also have a go() function that creates a variable named dog again, and sets it to the value of "sunny".
  4. Then we run that logDog() function, and then we run go();.

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!

console showing logged value as 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.

Editor showing code

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 as 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

  1. 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.
  1. 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.

console showing the logged name from sayHi function in uppercase

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, but generally, you won't be doing that.

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!

Edit on Github

Syntax Podcast

Hold on β€” I'm grabbin' the last one.

Listen Now β†’
Syntax Podcast

@wesbos Instant Grams

Master Gatsby

Master Gatsby

Building modern websites is tough. Preloading, routing, compression, critical CSS, caching, scaling and bundlers all make for blazing fast websites, but extra development and tooling get in the way.

Gatsby is a React.js framework that does it all for you. This course will teach you how to build your websites and let Gatsby take care of all the Hard Stuffβ„’.

MasterGatsby.com
I post videos on and code on

Wes Bos Β© 1999 β€” 2021