Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save kpwahn/87bd8ca3dee0c1ebc1c5e0214fe996e7 to your computer and use it in GitHub Desktop.
Save kpwahn/87bd8ca3dee0c1ebc1c5e0214fe996e7 to your computer and use it in GitHub Desktop.

Blog Post Improvements

Identifying Improvements

The blog post I'm hoping to help improve is The Ultimate Guide to Hoisting, Scopes, and Closures in JavaScript.

Just a quick note, the YouTube video for this post links to The Evolution of Async JavaScript: From Callbacks, to Promises, to Async/Await when it should be linked here.

This post is absolutely fantastic. Execution context is critical to understanding so much about JavaScript, and approaching topics such as hoisting and closures from this perspective has made me a much better programmer.

However, JavaScript's "scope" doesn't fit well with execution context because its scope is lexical, not dynamic.

According to MDN's article on closures:

The word lexical refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available.

Kyle Simpson, author of You Don't Know JavaScript (first edition), states that lexical scope

is based on where variables and blocks of scope are authored by you at write time.

He also states:

Dynamic scope, by contrast, doesn't concern itself with how and where functions and scopes are declared, but rather where they are called from. In other words, the scope chain is based on the call-stack, not the nesting of scopes in code.

The Ultimate Guide to Hoisting, Scopes, and Closures in JavaScript explains scope and scope chain as if JavaScript had dynamic scope (runtime), which fits in really well with execution context; however, JavaScript has lexical scope (author time).

Let's walk through this example using dynamic scope. You can see how the JavaScript Visualizer behaves weirdly here.

var age = 100

function logAge () {
    console.log(age)
}

function logName () {
  var name = 'Tyler'
  var age = 25

  console.log(name)

  logAge()
}

logName()
  1. During the global creation phase, age is initialized to undefined, and both logAge and logName are put entirely into memory.
  2. During the global execution phase age is assigned the value 100 and logName is invoked.
  3. During logName's creation phase, age and name are initialized to undefined.
  4. During logName's execution phase, age is assigned the value 25, name is assigned the value 'Tyler', and logAge is invoked.
  5. During logAge's creation phase, nothing noteworthy happens.
  6. During logAge's execution phase, logAge does not have a local variable age so it looks to its parent's execution context. logName has an age variable, whose value is 25, so it logs that.
  7. logAge's execution context is removed.
  8. logName's execution context is removed.
  9. Global execution context is removed.

Now let's examine this code using lexical scope. All steps remain the same except for step #6:

  1. During logAge's execution phase, logAge does not have the local variable age so it looks outside the scope of where it's written. Global scope has an age variable, whose value is 100, so it logs 100.

As you can see, this code behaves differently depending on whether or not your programming language uses lexical or dynamic scope. JavaScript uses lexical scope.

In the blog post's video, the following statement is made:

Whenever that happens [, having a function nested inside of another function,] the inner function is going to create what's called a closure over the outer function's execution context.

But in reality, it's really just creating a closure over the lexical scope, defined by where the code is written.

Suggestions On How To Improve

In order to improve this blog post, scope needs to be taught separately from execution context.

Scope has to do with which functions or variables are accessible at a given point in a program's execution. Javascript has three different types of scopes:

  • global scope
  • function scope
  • block scope

Global scope

Any variables not inside of curly braces {} are a part of global scope. These variables can be accessed from anywhere in the application.

// declared in global scope. Accessible anywhere
var age = 100

function logName () {
  var name = 'Tyler'
  var age = 25

  console.log(name)
}

logName()

Function scope

Any variables declared inside a function are a part of that function's scope and can only be accessed inside of that function. They cannot be accessed outside of the function's curly braces.

var age = 100

function logName () {
  var name = 'Tyler'
  var age = 25

  console.log(name)
}

logName()

// Cannot access name, it's only accessible inside of logName's function scope!
console.log(name)

Block scope

Any variables declared in a block, delineated by curly braces {}, are only accessible inside of that block.

var age = 100

function logName () {
  var name = 'Tyler'
  var age = 25

  if (age > 50) {
    let message = `${age} is more than halfway to 100!`
  }

  // Cannot access message, it's only accessible inside of the if statement's block scope
  console.log(message)
}

logName()

Side note let and var behave differently because of scope! You can read more about it here.

Function and block scopes can be nested. This is important because whenever javascript references a variable that isn't in scope, it looks to its nearest encompassing scope, also referred to as the parent's scope.

var age = 100

function logAge () {
  // Age is not in my functions scope, let's look at my parent's scope, which is global scope
  console.log(age)
}

logName()

The process of JavaScript going to each parent's scope one by one if a variable doesn't exist is called the scope chain.

Closures:

Formally put:

A closure is the combination of a function and the lexical environment within which that function was declared.

This lexical environment consists of any local variables that were in-scope at the time the closure was created.

How do we know what is "in-scope" for a function? We need only look to our scope chain!

var count = 0

function makeAdder(x) {
  return function inner (y) {
    return x + y
  }
}

var add5 = makeAdder(5)
count += add5(2)

Taking a look at inner's scope chain, we have

  1. inner's function scope
  2. makeAdder's function scope
  3. global scope

Now no matter where we invoke inner, it maintains its scope.

Because of closures, the function inner, when invoked as add5, has access to x because x is part of its scope chain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment