/u1_lesson_js_scope

This lesson digs deeper into the concept of scope and it's importance in JavaScript.

JavaScript Scope

Scope

Lesson Overview

In this lesson, we'll get deeper into the idea of scope in JavaScript and why it's such an important concept to understand.

Objectives

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

Lesson Instructions

Discussion

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?

Lego

Blocks

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.

Scope

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:

Demo - creating block scope

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 */ }

Demo - global and local 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.

whew

Best Practices:

  • 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 (prefer const over let, but never use var)

Lesson Recap

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.

Cap

Resources