All I Know About: Scope in JavaScript
Tags: javascript, beginners
Published: 2020-10-19 00:37:03

Based off some of the bootcamp students I tutor, "scope" is either not something taught in their curriculum, or at most, briefly touched upon. However, understanding scope plays a huge factor in being able to debug and fix your own code. I'm here to shine a light on scope in JavaScript and why it's such an important concept to understand. If you're someone who understands each line of the code below, but you're unable to tell why it returns an error, you've come to the right place!

```js
if(true) {
  let someVar = "Foo"
}
console.log(someVar) //-> ReferenceError: someVar is not defined
```

## Defining Scope

Before discussing the code above, let's actually define scope. Here's what I ripped right out of [MDN's documentation](https://developer.mozilla.org/en-US/docs/Glossary/Scope):

> Scope - The current context of execution. The context in which values and expressions are "visible" or can be referenced.

Make sense? No? Don't worry, MDN's documentation takes some getting used to and may not make sense when you're just starting out. Let me break down some of the words.

First, "referencing" a variable. Some of you may understand the difference, but let me quickly explain declaring, defining, and referencing.

```js
// this is declaring, but not defining
let myVar;

// this is declaring and defining on a single line
let otherVar = 10;

// this is referencing a variable that has already been declared
console.log(otherVar); //-> 10

// this is referencing a previously declared variable
// and defining its value
myVar = 50; 

// this is referencing a previously declared variable
// and re-defining its value
otherVar += 20; //-> otherVar now equals 30
```
Referencing a variable is calling a variable that has already been declared before. If you try and reference a variable that hasn't been declared yet, you get an error. Likewise, if you reference a variable that has been declared but hasn't been defined, you'll get an undefined value and no error. Like so:

```js
let myVar;

// try to reference a variable that was never declared
console.log(otherVar); //-> ReferenceError: otherVar is not defined;

//try to reference a variable that WAS declared but never defined
console.log(myVar); //-> undefined
```

In the case of context, just think of it as the surrounding rules of how code is read or how a variable is used. Without context, any variable could be read from any part of a js file, or worse, if we have multiple files, a variable could be declared in one file but re-defined or referenced in a different file even if that wasn't our intention. It would be anarchy! Example:

```js
// fileA.js
let count = 10;
```

```js
// fileB.js
let count = 2;
```

```js
// fileC.js
console.log(count); //-> ???
```

Without any context telling us the rules for each `count` there would be no way to tell `fileC` which `count` to log since we have two `count` variables from two different files. And that's what scope is. It's just giving our code some context as to how and where our variables can be referenced. Once we get into the types of scope, this will all start setting in.

## Types of Scope

JavaScript has a handful of different kinds of scope. One way we can tell our code what kind of scope we want to use is by adding a `var`, `let`, or `const` before the variable name when declaring our variable. This keyword is what tells JavaScript how we want to scope the variable. 

### Block Scope: `let` and `const`

We'll talk about `let` and `const` first since it's considered the new standard after their premiere in ES6 and they're probably what you're using right now anyway. *I'll explain what ES6 is in a later post, but for now just know it is a feature release made by the top brass who are hard at work, standardizing JavaScript along with other languages.*

`let` and `const` variables use what we call **block scope**. Anytime you've ever seen curly braces in your code, that represents a block of code. Block scope means that your variable is only readable and writeable within the block it was declared in. This is a perfect time to bring back our problem at the very beginning! Let's look at that again:

```js
if(true) {
  let someVar = "Foo"
}
console.log(someVar) //-> ReferenceError: someVar is not defined
```

Notice how `someVar` is declared *inside* of the curly braces of the if statement, but we try to call the variable *outside* of those curly braces. Block scope tells JavaScript that we only want our variable to exist inside of the block it was declared in. Anything outside of the block will have no *reference* to the variable in the block, hence the `ReferenceError` we're getting. If we were to move the console log inside of the block, we would be able to log `someVar` since it would be within the scope:

```js
if(true) {
  let someVar = "Foo"
  console.log(someVar) //-> "Foo"
}
```
Likewise, if we had child blocks in our block, that is, if we had other blocks inside our block, those children will have access to variables declared in their parent.

```js
// parent block of code
if(true) {
  let color = "orange";
  
  // child block of code inside parent
  if(true) {
    console.log(color); //-> "orange"
  }
}
```
No matter how many children or grandchildren the parent block has, the children will always have access to variables declared inside any of their parents, grandparents, etc. However, parent blocks of code cannot reference variables that were declared in one of their children. 

```js
if(true) {
  if(true) {
    if(true) {
      // create variable in a child block
      let color = "green";
    }
  }
  // try to reference the variable  
  // at a parent block
  console.log(color); //-> ReferenceError: color is not defined
}
```

So what if we need to define a variable in a child block, but then reference that variable in a parent block? Let's say you have a function (parent block) and in the function you want to create a variable if some condition is true, but you still have to return the variable at the end of the function. All you have to do is declare the variable in the parent block before the child block:

```js
//parent block
function someFunc() {
  // declare variable in parent block
  let myVar;
  if(true) {
    // define variable in child block
    myVar = "It was true!";
  }
  // reference variable back in parent block
  return myVar;
}
```
As you can see, even though we *defined* `myVar` in a child block, we can reference it in the parent block because it was *declared* in the parent block.

You might be wondering what the difference is between `const` and `let` is since they both have the same exact scope. While it's true that they both share the same scope, `const` variables cannot be mutated from its original definition. For instance:

```js
const firstName = "Keith";
firstName = "George"; //-> TypeError: Assignment to constant variable.
```
Whereas let can be changed how ever many times you want.

```js
let lastName = "Charles";
lastName = "Richards";
lastName = "Urban";

// no errors with this!
```

This helps to store data and prevent it from ever being changed, such as storing a url like `"http://facebook.com"`. It's pretty safe to assume facebook's url will never change, so to give your code some added security we can store that url in a const variable, and we'll sleep soundly knowing a new line of code won't ever inadvertently change the value of that variable.

### Global Scope: `var`, `let`, and `const`

When a variable is declared outside of any function or block of code, regardless of if you're using `var` `let` or `const`, it is considered **Globally Scoped**. What this means is that any inner scope has access to reference a globally scoped variable. Example:

```js
// variable declared outside of any function or block
let iceCream = "chocolate";

console.log(iceCream); //-> "chocolate"

if(true) {
  console.log(iceCream); //-> "chocolate"
}

function giveMeIceCream() {
  console.log(iceCream); //-> "chocolate"
  if(true) {
    console.log(iceCream); //-> "chocolate"
  }
}
```

No matter where you are in your code, you will always have access to globally scoped variables. Again, using `const` would have the same effect as `let`, as would `var` in this case. However `var` goes a little further, adding your variable as a property of the global `window` object. Here's an example:

```js
var myName = "Keith";

console.log(window.myName); //-> "Keith"
```
This is the truest form of "Global" as the `window` object is always accessible no matter where you are in your file, and no matter what file you're in inside your app/website. 

### Functional/Local Scope: `var`

`var` is the only keyword that creates a **Functional Scope** also known as **Local Scope**. That just means that a variable declared inside of a function can be referenced anywhere within that function, regardless of any blocks that may be in the code. Example: 

```js
function myFunc() {
  if(true) {
    // declare variable with var (function scope)
    var someVar = "Bar";
  }
  // can call any var variable within the same function
  // regardless of block difference
  console.log(someVar); //-> "Bar"
}

myFunc();
// someVar only exists within the function
// it was declared inside of
console.log(someVar); //-> ReferenceError: someVar is not defined
```
In the example, we can see how functional scope differs from block scope. With block scope (if we declared the variable with a `let` instead of a `var`, the first console log would result in an error because the log is outside of the if statement where the variable is declared, but with functional scope we can access the variable anywhere within `myFunc`. As for the other console log outside of `myFunc`, we get an error because we're outside of the function, therefore outside of the scope of `someVar`.

## Other Scopes

Once you've grocked all that we discussed above, we can get into the slightly more complicated versions and aspects of scope in JavaScript.

### Module Scope

If you've used JavaScript libraries like React or if you've used ES6 modules where you `export` parts of one js file and then `import` them into another file, then you've run into **Modular Scope**. Modular scope prevents code from accessing variables or functions from other files unless you explicitly `export` that variable from the file and then `import` it to the file you're trying to use it in. Here's an example without modular scope: 

```js
// fileA.js
const myName = "Keith";
```
```js
// fileB.js
console.log(myName); //-> ReferenceError: myName is not defined
```

Here, `fileB` has no idea what `myName` is, therefore it cannot log it from within the bounds of its file. However if we were to `export` `myName` from `fileA` then import it to `fileB`:

```js
// fileA.js
const myName = "Keith";

export {myName}
```
```js
// fileB.js
import {myName} from 'fileA.js';
console.log(myName); //-> "Keith"
```
Now that `fileB` knows where to grab `myName` from, we can easily access the variable and call it whenever we want from `fileB`.

### Lexical/Static Scope

**Lexical scope** also known as **static scope** deals with functions within functions, or *nested functions*. When you nest functions together the variables inside those functions use the scope that was in place when the functions were first defined. For example:

```js
let someVar = "I'm global scoped!"

function funcA() {
  let someVar = "I'm block scoped"
  function funcB() {
    console.log(someVar);
  }

  return inner;
}

const lexicalScope = outer();

console.log(someVar); //-> "I'm global scoped!"
console.log(lexicalScope()); //-> "I'm block scoped"
```

So what the heck is going on here? Let's break it down. we first define `someVar` globally. Then we create `funcA` and in it, redefine `someVar` as a block scoped variable. Next we create `funcB` that just logs `someVar` which we're grabbing from `funcA due to block scoping (`someVar` is declared in a parent block so we can access it in a child block). Then we return `funcB` at the end of `funcA`. Outside of the functions we invoke `funcA` and set it inside of our `lexicalScope` variable. This will give us `funcB` in return. Finally, we console log `someVar` which gives us our global variable value. And we console log the invocation of `funcB` which gives us our block scoped variable. 

We're calling `funcB` outside of `funcA`, so how are we still accessing the `someVar` inside of `funcA`? Well, I'll reiterate: When you nest functions together the variables inside those functions use the scope that was in place when the functions were first defined. When `funcB` was first defined, the scope of `someVar` was block scoped because of the variable we declared in `funcA` which was the parent block of `funcB`. Therefore, whenever we call that nested inner function, `funcB`, we grab the variable it referenced when it was first defined, not the globally scoped variable.

## Wrapping it up

As you can see, there are a number of different scopes to keep in mind when coding in JavaScript. Don't worry if you need to come back to this as a reference from time to time! It will take a minute before you get a full grasp of every kind of scope JavaScript touts. Just keep an eye on *where* you're declaring your variables, and remember what scope the keyword you're using encompasses. (But you should really be using `let` and `const` at this point!) ✌