Scope is a key concept in JavaScript that can feel excruciatingly painful to learn as a beginner. However, if you can recognize the drawbacks to leaving unattended dogs with your expensive furniture, can understand the pain of being assigned the wrong spirit animal or can even recall the process of requesting a sugary snack as a child, you’ll have a grasp on the basics of scope in no time.

Scope explained by dogs capable of mass destruction

Photo by Jennifer Custer at Sh\*t My Pets Ruined

Photo by Jennifer Custer at Sh*t My Pets Ruined

My sister has two German Shepherds. When she leaves for work, she puts up a gate to keep the younger dog in the long hallway. This is because my sister would like to still have furniture when she comes back home. The older dog, who is slightly less inclined to eat the couch, is able to hang out in the living room. Each dog is left with a toy to play with in their respective areas.

Of course, when my sister returns home from work, she puts the gate away and the dogs can roam around and fight over the same toy all they want.

So how does this apply to JavaScript? In JavaScript, there are three types of scope: global scope, function scope and block-level scope.

Global Scope: This is any code that is written outside of a function. We've all seen it in the standard beginner-level JavaScript tutorial.

  var greeting  = "Hello World";

Is the code wrapped in a function? Nope. So it’s in the global scope.

Code in the global scope is available to any piece of code, no matter how deeply nested it is. It's a lot like how when my sister removes that gate the dogs can go and play with any toy or go to any part of the apartment. In other words, the entire apartment and its contents are all now available to them to do with as they please.

To get a little more technical, global variables are really properties of the window object. This means the following code snippets are the same:

  var toy = "squeaky toy";
window.toy = "squeaky toy";

Function Scope: Function scope is also called local scope. Any variables declared in a function can only be accessed within that function. The function scope acts like the gate in our example. Because it fortunately hasn’t occurred to either dog yet that they can just hop over the gate, we'll say that the dogs can only access the toys in their own local space.

Block-level Scope: As Kyle Simpson writes in his book You Don’t Know JS: Scope & Closures, before the adoption of let and const there wasn’t really any way to use block scope in JavaScript unless you were using with or try/catch.

Some people coming from other programming languages assume that if they were using something like a for loop that their variables would naturally be contained within the code block (or between the curly braces).

This is not the case in JavaScript. For us, these variables actually end up in the global scope. Surprise! Using our dog example, this is like us assuming a too short gate would keep the dogs in their respective spots. But we would learn otherwise when we came home to discover we’d need to be taking an unexpected trip to the furniture store. The usage of let and cost is a topic for a different discussion, but do know that they allow us to create block-level scope in JavaScript.

So why do we need to be concerned with scope? For the same reason that we crate puppies and give teenagers curfews. Left to their own devices, they're going to encounter variables we can't predict and bad things will happen. JavaScript is good enough at making life difficult when it's behaving as expected. Understanding the rules of scope gives us the means to safeguard some of our sanity.

Sea otters or lions? A tale of a spirit animal identity crisis

As we discussed earlier, code in the global scope is accessible to everything and anything. This leads to unpredictable behavior. Unpredictability leads to bugs and aggravation. This is why we use "use strict" when we start a new JS file. This is why we typically use a triple equals instead of double equals unless we have a reason to do so. Flexibility is what makes JavaScript such a powerful language, but this flexibility can bite you in the ass especially in a large code base.

To give an example of how global scope can ruin our day, let's use my teammate as an example. My teammate’s spirit animal is a sea otter, which pretty much goes to show how cool he is. Unless he's talking to his wife, it turns out, and then his spirit animal becomes a lion.

Imagine we had two files, everyone.js and wife.js. In everyone.js we're going to set his spirit animal to be sea otter and in wife.js we'll set it to be lion. We'll need to load these scripts in one at a time in our HTML file. But when we try console.log(spiritAnimal), we see that something unexpected happens. We're told that his spirit animal is a lion, which everyone but his wife knows is nonsense.

  //everyone.js 
var spiritAnimal = "sea otter";

  //wife.js
var spiritAnimal = "lion";

  <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Spirit Animals</title>
</head>
<body>
  <script src="everyone.js"></script>
  <script src="wife.js"></script>
</body>
</html>

So what happened? Since both of these variables are set in the global scope and have the same name, the first one gets overwritten.

In an example this small, it's not hard to spot what happened. But if you look at a large codebase with tons of global variables all with the same name, and multiple JavaScript files being merged together, you can see the potential to waste hours trying to figure out how some value got randomly altered. So for our example, my teammate’s spirit animal is a manly lion instead of a playful sea otter. In real life, these global namespace conflicts can potentially break your site.

Of course, that leaves us with the following question: how do we protect our code from the evils of global scope?

Protect the sea otter: using scope to restore one’s true identity

Do you remember how we said that code in the global scope isn’t wrapped in a function? If we don’t want global variables, functions are our new favorite tool.

One approach to preventing variable leakage into the global scope is to wrap code in an IIFE. IIFE stands for Immediately Invoked Function Expression. The name is important to remember because it gives us clues on how to set it up. Working backwards, we can break it down as follows:

Function Expression: An IIFE is just a function that is wrapped in parentheses. Why is our function wrapped in parentheses? Because it is a function expression, not a function declaration. If we forget the parentheses, our browser will assume we meant to use a function declaration and will throw an error. (Note: If you’re not clear about the differences between a function expression and a function declaration, I’ll be writing another post about it soon.)

Immediately Invoked: Functions all have a simple rule: there must be a way to invoke them. Here, we invoke our IIFE by adding the second set of parentheses after our closing parenthesis.

So the basic format is as follows:

  (function() {
  //code goes here….
})();

My teammate obviously wants to be associated with the proper spirit animal depending on who he is talking to, so if we go back and wrap our code in an IIFE, it’s no longer possible to accidentally overwrite our variables.

  //everyone.js 
(function() {
  var spiritAnimal = "sea otter";
})();

  //wife.js
(function() {
  var spiritAnimal = "lion";
})();

The scope chain, nesting and hyperactive children

When I was young and wanted a treat, I would start by asking whichever adult was in charge of me if I could have a snack. If they agreed, I'd get my sugar right away and get to work on devouring it. Unfortunately, when that adult was my mother, she immediately pictured the sugared up version of me and usually said no. Naturally this meant I'd have to go and ask my father before my mother spread word of the sugar ban. In the off chance my father also said no, I'd have work my way through all available adults until I either got my snack or ran out of adults to ask and was left sad and snackless. And yes, before you ask, I did learn to go straight to the adult most likely to say yes - my grandmother.

The scope chain is a lot like the adults who control access to the cookie jar. Since functions can be nested inside of other functions, when you want to access a piece of information your code first checks in its immediate surroundings, just as I asked my supervising adult first. If the piece of information your code is looking for isn’t available, it will work its way into the surrounding scope. It continues this process until it ultimately reaches the global scope. If the information is available in the global scope, you're able to access it and use it (snack time!) and if not you are told you have a ReferenceError (sad face).

Conclusion

There is an incredible amount to learn about scope. This article was developed out of a presentation I gave my team and our discussion here only scratches the surface the topic. Hopefully it lays a good foundation so you can start digging deeper. I plan on updating the article as I learn more about scope, but in the meantime if I’ve gotten anything wrong please let me know!


663 3 12