Scoping in Javascript

Where and when variables will be accessible in your code.

Scoping is important because we want to as much as possible follow the Principle of Least Privilege when writing programs — that is, if you don’t need to reveal your variable to the rest of your program, then keep its scope isolated. This important for many reasons, including reducing the chance of creating bugs/side effects, and increasing the efficiency of garbage collecting of variables while your program is running.

There are many articles out there on this topic so I wanted to really quickly break it down in a way that makes sense to me in 2020 (because things do change as technology and browser support change in the JS community).

One Scope To Rule Them All

There is really one type of scope if you are writing JS in ES6, and that is the block scope, or local scope. Function scope behaves the exact same as block scope, as long as you are using the ES6let keyword to declare your variables.

The reason that so many articles are written on this topic is that before the introduction of let and const keywords in ES6, variables declared with the var keyword would actually operate differently if declared or initialized in a function or in a block. A great example of this discrepancy is below : did you know that you can actually access ‘i’ from outside your for loop? This is because of the weird behavior of var. For the rest of the article I will use the keyword let to define my variables.

for(var i = 0; i < 10; i++){ 
console.log(i);
}
console.log("i : " + i) // prints "i : 10"

Before we get into what block scope is, let’s talk about the global scope.

Global Scope

Variables that are not scoped at all are referred to as being in the “Global Scope” that is essentially saying that they are accessible at the root of the document. This is pretty easy to understand — they are accessible inside any function, and any block on the page.

var global = "global";
var i = 0;
while (1 < 10) { console.log(global); // prints "global" ten times
i ++;
}

If you define a variable without using any keyword, inside a block, it will default for the global scope, unless you specify ‘use strict’ at the top of your document.

function scoped () {   test = 5;
console.log(test) // prints 5
}
scoped();
console.log(test) // prints 5

Block Scope

Is the following line of code valid JS? The answer is yes, it is just three nested block scopes.

{{{}}}

Brackets on their own, and also any brackets related to looping, function definition or declaration, etc. in your JS code will create a block scope for the variables within them. This means that you can reuse those variables in different block scopes, and you can only access the variables created inside the block from inside the block. A synonym for block scope is Local Scope. Additionally Function Scope behaves the exact same as block scope, as long as you are using the ES6let keyword to define your variables.

The simplest way to define a block scope is like this:

{
// block scope
let fruit = "apple"
console.log(fruit) // returns "apple"
}
// global scope
console.log(fruit) // fruit is not defined

Any other operation that uses brackets creates the same type of variable scope. This includes if else statements, loops, etc. For example:

if (true) {   let fruit = "apple"
console.log(fruit) // returns "apple"
}console.log(fruit) // fruit is not defined

Block scope is nested, and child blocks can access the variables inside of parent blocks. Accessing variables of the parent in the child is called “Lexical Scope”. This is another term you might hear related to these scoping concepts, and it is important because the lexical scope defines what the JS engine actually sees when it is executing a function in your JS.

// global scope
for (let i = 1; i < 10; i ++) {
// block scope 1
let fruit = "apple"
console.log(fruit) // returns "apple"

for (let j = 1; j < 10; j ++) {
// block scope 2
// the variable "fruit" is also defined in the lexical scope
let citrus = "orange"
console.log(citrus) // returns "orange"
}
console.log(citrus) // citrus is not defined
}console.log(fruit) // fruit is not defined
console.log(citrus) // citrus is not defined

An important note is that object definition is not block scoping, though it looks similar. It is just simple assignment of a variable. The all properties of a generated object are still accessible from the global scope:

var fruitStore = {

inventory: 2
}console.log(fruitStore.inventory) // returns 2

More Terms

I hope that this makes things clearer to you. I also want to introduce a couple related topics that you might hear in close proximity to the topic of scope.

Closure : A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

Hoisting : Hoisting is the behavior of variables and functions that are referenced before they are initialized. keywords var and let work differently when it comes to their hoisting behavior.

Thanks — I hope you enjoyed reading.

Alex Zito-Wolf

Product Manager + Software Developer. Interested in Travel, Culture, and the Internet.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store