In this lesson, we'll get deeper into the idea of scope in JavaScript and why it's such an important concept to understand.
- Learn what a block is.
- Discuss the difference between global and local scope in JavaScript.
- Identify which part(s) of JavaScript create new scope.
- Identify which variables are accessible in various scopes.
One best practice when coding is to only allow a piece of code to access the things it needs to access, and nothing more. To reduce interdependency and lower coupling, we can separate code into groups, called blocks, and create different containers to hold our variables, called scope.
Why might software developers want to keep certain objects and data separate from other parts of an application?
A Block statement is used to group code together. To create a block, we use a pair of curly braces:
{
// Statements
}
Optionally, a block can be labeled as a visual identifier or as a target for break.
Blocks are also used with functions, conditionals and loops:
if ( /* true || false */ ) { /* within the block, body of conditional */ }
for ( /* let i = 0; ...*/ ) { /* within the block, body of loop */ }
while ( /* i < num ... */ ) { /* within the block, body of loop */ }
function ( /* arg1, arg2 */ ) { /* within the block, body of function */ }
In addition to grouping code together, blocks create a new scope for the variables defined within the block.
When we use blocks, we create a new scope for the variables defined within the block. Within a block, if we are using the ES6 let
and const
variables (which you should, and we'll discuss next week), these variables have block scope, meaning the variables defined within the block are limited in scope to the block in which it is defined:
const name = 'Danny'
{
const name = 'Caleb'
}
console.log(name) // prints 'Danny'
// name = 'Caleb' is limited in scope to the block in which it is defined
You can think of scope as a collection of nested boxes. Each scope acts as a container in which variables and functions can be declared. while JavaScript is executing code within a scope, it only has access to identifiers declared in the current scope and higher scopes, the parent and global scopes.
Scopes in JavaScript come in two flavors: block scope and function scope. When you create a function, you isolate the scope within that function. Within the function, you can access the local scope and the parent scopes, but outside the function, you cannot see or access the scope within the function. The function's contents are private and are accessible only within that function.
We can create scope by using functions and blocks:
{ /* creates block scope */ }
if { /* creates block scope */ }
for ( /* ... */ ) { /* creates block scope */ }
while ( /* ... */ ) { /* creates block scope */ }
function ( /* ... */ ) { /* creates a function scope */ }
Let's see some more code examples of scopes.
Remember that block scope means our different scopes are separated by blocks { }
.
// I am not inside a block
if (true) {
// i am inside a block
}
// I am not inside a block
NOT objects but blocks.
if (true) {
// i am inside a block
}
const obj = {
prop1: 'I am not inside a block',
prop2: 'This is an object silly'
}
The outer most scope is the global scope and all inner scopes are considered local scopes:
// global scope
if (true) {
// local scope
}
// global scope
Variables are accessible within the scope they are declared:
// global scope
if (true) {
// local scope
const x = 1 // what would happen if `var` were used instead?
console.log(x) // 1
// When should we use `console` functions?
}
// global scope
console.log(x) // ReferenceError: x is not defined
Variables are accessible to any inner scopes (child scopes):
// global scope
let x = 1
if (true) {
// local scope
x = 2
console.log(x) // 2
}
// global scope
console.log(x) // 2
But not to the scopes above them (parent scopes):
// global scope
const x = 1
if (true) {
// local scope
const y = x
console.log(y) // 1
}
// global scope
console.log(x) // 1
console.log(y) // ReferenceError: y is not defined
Variables are not accessible from sibling scopes:
if (true) {
// local scope of 1st sibling
const a = 1
console.log(a) // 1
}
if (true) {
// local scope of 2nd sibling
console.log(a) // ReferenceError: a is not defined
}
Different scopes can have variables that are declared with the same name and they do not conflict or know about each other.
// global scope
const x = 1
console.log(x) // 1
if (true) {
// local scope
const x = 2
console.log(x) // 2
}
// global scope
console.log(x) // 1
So that means a variable declared in the global scope is accessible by all of the scopes we create and a variable declared in a local scope is only accessible to itself and its child scopes.
As we have seen, utilizing scope provides great utility. We get more control over who can access and manipulate our data. We can use scope to declare a variable without polluting the global namespace. Scoping provides a way to encapsulate data and prevent other parts of our applciation from accessing variables declared within a certain scope.
When you are not familiar with the rules of scope, it will be a common source of bugs and frustration. By being aware of how scope is created, and by using scope effectively, you will write code that is more efficient, organized and less error prone.
- Apply the Principle of Least Privilege: Allow code to access the information and resources that are necessary for it to run, and nothing more.
- Encapsulate code as much as possible in scope using functions and blocks
- Never use
var
(preferconst
overlet
, but never usevar
)
In JavaScript, we use scope to encapsulate data and hide it from other parts of our application. The most common way to create a new scope is with a function. We can also create scope using a block. By using scope, we can keep our code organized, manageable, avoid variable name collision, and keep the global namespace clean.