Skip to content

Instantly share code, notes, and snippets.

@kdipaolo
Last active October 30, 2018 20:14
Show Gist options
  • Save kdipaolo/cefb522228c1b43bbee36c3e31bc7400 to your computer and use it in GitHub Desktop.
Save kdipaolo/cefb522228c1b43bbee36c3e31bc7400 to your computer and use it in GitHub Desktop.

Execution Contexts

Global Execution Context

The first Execution Context that gets created when the JavaScript engine runs your code is called the “Global Execution Context”.

in the Global Creation phase, the JavaScript engine will:

  1. Create a global object.
  2. Create an object called “this”.
  3. Set up memory space for variables and functions.
  4. Assign variable declarations a default value of “undefined” while placing any function declarations in memory.

Once we enter the Execution phase, the JavaScript engine starts executing the code line by line and assigns the real values to the variables already living in memory. Example:

// In the creation phase memory space is made for variables and set to undefined
console.log(name) // undefined
var name = 'Kurt'
// *they are not given their real values unitl the execution phase
console.log(name) // "Kurt"

This process of assigning variable declarations a default value of undefined during the creation phase is called Hoisting. Hoisting is a term that helps visualize the variable definitions getting "hoisted" to the top during the creation phase like so:

// Creation
var name = undefined
// Execution
console.log(name) // undefined
name = "Kurt"
console.log(name) // "Kurt"

// Actual 
console.log(name) // undefined
var name = 'Kurt'
console.log(name) // "Kurt"

Location function execution context

The only time an Execution Context is created is when the JavaScript engine first starts interpreting your code (Global Execution Context) and whenever a function is invoked.

Whenever a Function Execution Context is created, the JavaScript engine will:

  1. Create an arguments object.
  2. Create an object called this.
  3. Set up memory space for variables and functions.
  4. Assign variable declarations a default value of “undefined” while placing any function declarations in memory.

Execution Stack (Call Stack)

  • The JavaScript engine creates what’s called an “Execution Stack”.
  • Anytime a function is invoked, a new Execution Context is created and added to the Execution Stack.
  • Whenever a function is finished running through both the Creation and Execution phase, it gets popped off the Execution Stack.
  • The memory of the function get then gets garbarge collected.

Scope

“The current context of execution.”

function foo(){
 var bar = "Declared in foo"
}
console.log(bar)
// Uncaught ReferenceError: bar is not defined or undefined depending on strict mode
  1. When foo is invoked we create a new Execution Context on the Execution Stack.
  2. The Creation phase creates this, arguments, and sets bar to undefined.
  3. Then the Execution phase happens and assigns the string Declared in foo to bar.
  4. After that the Execution phase ends and the foo Execution Context is removed from the stack.
  5. foo is now gone, like it never existed.
  6. We try to log bar to the console, but we get undefined because the bar in foo is gone.

What this shows us is that variables created inside of a function are locally scoped. That means they can’t be accessed once the function’s Execution Context has been popped off the Execution Stack.

Scope Chain

If the JavaScript engine can’t find a variable local to the function’s Execution Context, it’ll look to to nearest parent Execution Context for that variable. This lookup chain will continue all the way until the engine reaches the Global Execution Context. In that case, if the Global Execution Context doesn’t have the variable, it’ll throw a Reference Error.

Closure

if you have a function nested inside of another function. In this case, the child function will still have access to the outer function’s scope, even after the parent function’s Execution Context has been removed from the Execution Stack.

this Keyword

The this keyword allows you to reuse functions with different contexts. Said differently, the this keyword allows you to decide which object should be focal when invoking a function or a method.

How to tell what the this keyword is referencing: “Where is this function being invoked?”.

function greet (name) {
  alert(`Hello, my name is ${name}`)
}
// FUNCTION IS BEING INVOKED BELOW
greet("Kurt")

Steps to check when trying to figure out what the this keyword is referencing:

  1. Implicit Binding
  2. Explicit Binding
  3. new Binding
  4. Lexical Binding
  5. window Binding

1. Implicit Binding

Look to the left of the dot when the function is invoked. If there is a “dot”, look to the left of that dot to find the object that the this keyword is referencing.

const user = {
  name: 'Kurt',
  age: 29,
  greet() {
    alert(`Hello, my name is ${this.name}`)
  }
}
//  this keyword is referencing the user object, because it is to the left of the dot where the function is being executed
user.greet() // "Hello, my name is Kurt"}

2. Explicit Binding

Expcility binding a function to its context, using either call, apply, or bind

Call

“call” is a method on every function that allows you to invoke the function specifying in what context the function will be invoked.

function greet (skillOne, skillTwo, skillThree) {
  alert(`Hello, my name is ${this.name} and I know ${skillOne}, ${skillTwo}, and ${skillThree}`)
}
const user = {
  name: 'Kurt',
  age: 29,
}
const skills = ["JavaScript", "React", "CSS"]
// The first argument you pass to call will be what the this keyword inside that function is referencing.
// The rest are any arguments you want the funciton to have
greet.call(user, skills[0, skills[1], skills[2]) // "Hello, my name is Kurt nad I know JS, React, and CSS"

Apply

Apply is the exact same thing as call, but instead of passing in arguments one by one, you can pass in a single array and it will spread each element in the array out for you as arguments to the function.

function greet (skillOne, skillTwo, skillThree) {
  alert(`Hello, my name is ${this.name} and I know ${skillOne}, ${skillTwo}, and ${skillThree}`)
}
const user = {
  name: 'Kurt',
  age: 29,
}
const skills = ["JavaScript", "React", "CSS"]
// The first argument you pass to call will be what the this keyword inside that function is referencing.
// The second argument is an array that gets spread out and used as arguments in the function being called.
greet.apply(user, skills) // "Hello, my name is Kurt nad I know JS, React, and CSS"

Bind

Bind is the exact same as call but instead of immediately invoking the function, it’ll return a new function that you can invoke at a later time.

function greet (skillOne, skillTwo, skillThree) {
  alert(`Hello, my name is ${this.name} and I know ${skillOne}, ${skillTwo}, and ${skillThree}`)
}
const user = {
  name: 'Kurt',
  age: 29,
}
const skills = ["JavaScript", "React", "CSS"]
const newFunction = greet.bind(user, skills[0, skills[1], skills[2]) 
newFunction() // "Hello, my name is Kurt nad I know JS, React, and CSS"

3. new Binding

Whenever you invoke a function with the new keyword, under the hood, the JavaScript interpretor will create a brand new object for you and call it this. So, naturally, if a function was called with new, the this keyword is referencing that new object that the interpretor created.

function User (name, age) {
  /*
    Under the hood, JavaScript creates a new object called `this`
    which delegates to the User's prototype on failed lookups. If a
    function is called with the new keyword, then it's this new object
    that interpretor created that the this keyword is referencing.
  */

  this.name = name
  this.age = age
}

const me = new User('Tyler', 27)

4. Lexical Binding

With arrow functions, this is determined “lexically”. Arrow functions don’t have their own this. Instead, just like with variable lookups, the JavaScript interpretor will look to the enclosing (parent) scope to determine what this is referencing.

5. window Binding

window binding is the “catch-all” case.

function sayAge () {
  console.log(`My age is ${this.age}`)
}

const user = {
  name: 'Tyler',
  age: 27
}
// Nothing to the left of the dot (implicit), not using .call, .apply, or .bind (explict), no new keyword (new binding) so JavaScript is // defaulting this to reference the window object. 
sayAge() // My age is undefined
window.age = 29

function sayAge () {
  console.log(`My age is ${this.age}`)
}
sayAge() // "My age is 29"

As of ES5, if you have “strict mode” enabled, JavaScript will do the right thing and instead of defaulting to the window object will just keep this as undefined.

'use strict'

window.age = 29

function sayAge () {
  console.log(`My age is ${this.age}`)
}

sayAge() // TypeError: Cannot read property 'age' of undefined

this summary:

  1. Look to where the function was invoked. Where the () are.
  2. Is there an object to the left of the dot? If so, that’s what the this keyword is referencing. If not, continue to #3.
  3. Was the function invoked with “call”, “apply”, or “bind”? If so, it’ll explicitly state what the this keyword is referencing. If not, continue to #4.
  4. Was the function invoked using the “new” keyword? If so, the this keyword is referencing the newly created object that was made by the JavaScript interpretor. If not, continue to #5.
  5. Is this inside of an arrow function? If so, its reference may be found lexically in the enclosing (parent) scope. If not, continue to #6.
  6. Are you in “strict mode”? If yes, the this keyword is undefined. If not, continue to #7.
  7. JavaScript is weird. this is referencing the “window” object.

The Evolution of Async JavaScript: From Callbacks, to Promises, to Async/Await

Callbacks

First, just as you can pass a string or a number as an argument to a function, so too can you pass a reference to a function as an argument. When you do this the function you’re passing as an argument is called a callback function and the function you’re passing the callback function to is called a higher order function.

function add (x,y) {
 return x + y
}

function higherOrderFunction (x, callback) {
 return callback(x, 5)
}

higherOrderFunction(10, add) // 15

The benefits are you can pass a function a callback and wait or pass it to another function. This gives the ability for a function to go get data and wait to call the callback unti the data has returned.

The problesm are When you have nested callbacks inside of nested callbacks, it forces you out of your natural way of thinking. Bugs happen when there’s a disconnect between how your software is read and how you naturally think. (Callback Hell). Another problem is since you’re not the one calling the callback function, you have 0 control over when and with what argument it’s invoked. Most of the time this isn’t an issue, but when it is, it’s a big one.

Promises

  • Promises exist to make the complexity of making asynchronous requests more manageable.
  • a Promise can be in one of three states, pending, fulfilled or rejected that represent the status of an asynchronous request.
const promise = new Promise()

The Promise constructor function takes in a single argument, a function. This function is going to be passed two arguments, resolve and reject.

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve() // Change status to 'fulfilled'
  }, 2000)
})

When you create a new Promise, you’re really just creating a plain old JavaScript object. This object can invoke two methods, then, and catch.

When the status of the promise changes to fulfilled, the function that was passed to .then will get invoked. When the status of a promise changes to rejected, the function that was passed to .catch will be invoked.

function onSuccess () {
  console.log('Success!')
}

function onError () {
  console.log('💩')
}

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve()
  }, 2000)
})

promise.then(onSuccess)
promise.catch(onError)

Async/Await

What if we just wrote our asynchronous code the same way which we write our synchronous code? If we did, that problem would go away entirely and it would still read sequentially.

$("#btn").on("click", async () => {
 const user = await getUser('tylermcginnis')
 const weather = await getWeather(user.location)

 updateUI({
   user,
   weather,
 })
})
  • async functions return a promise, even if the function is empty it still returns a promise.
  • For error handling, the most common approach is to wrap your code in a try/catch block to be able to catch the error.

Prototype

  • Every function in JavaScript has a prototype property that references an object.
  • It allows us to share methods across all instances of a function

1. Object Creation

The most common way to create an object is with curly braces {} and you add properties and methods to an object using dot notation.

Example: https://jsbin.com/ruwarir/edit?js,console

2. Functional Instantiation

Now odds are in our application we’ll need to create more than one person. Naturally the next step for this would be to encapsulate that logic inside of a function that we can invoke whenever we needed to create a new person. We’ll call this pattern and we’ll call the function itself a “constructor function” since it’s responsible for “constructing” a new object.

Example: https://jsbin.com/qemigoz/1/edit?js,console

3. Functional Instantiation with Shared Methods

The biggest problem so far wit this approach is the two methods on the person object. There’s no reason to re-create those methods as we’re currently doing whenever we create a new pesron. We’re just wasting memory and making each person object bigger than it needs to be. Instead of re-creating those methods every time we create a new person, we can move them to their own object and have each person reference that object. We can call this pattern.

Example: https://jsbin.com/sakakob/edit?js,console

4. Functional Instantiation with Shared Methods and Object.create

Let’s improve our example once again by using Object.create. Object.create allows you to create an object which will delegate to another object on failed lookups. Put differently, Object.create allows you to create an object and whenever there’s a failed property lookup on that object, it can consult another object to see if that other object has the property.

Example: https://jsbin.com/dogukuf/edit?js,output

5. Prototypal Instantiation

It seems “hacky” to have to manage a separate object (personMethods) in order to share methods across instances. That seems like a common feature that you’d want to be implemented into the language itself. Turns out it is and it’s called prototype. We can now put each shared method from personMethods on the Person's prototype.

Example: https://jsbin.com/ledexaq/1/edit?js,console

6. Pseudoclassical Instantiation

The new keyword creates the the object using Object.create() under the hood and also returns the new object under the hood, and the object that is created is called this.

the new keywork does 4 things:

  • creates a new object (let person = {})
  • sets this to the new object
  • links the object to its prototype (Object.create())
  • return the newly created object (return person)

Example: https://jsbin.com/hudisip/edit?js,console

7. Classes

In 2015, EcmaScript (the official JavaScript specification) 6 was released with support for Classes and the class keyword. This new class way is primarily just “syntactical sugar” over the existing way we’ve called the pseudoclassical pattern. In order to fully understand the convenience syntax of ES6 classes, you first must understand the pseudoclassical pattern.

Example: https://jsbin.com/cekeyeb/1/edit?js,console

Class misc

Static Methods

Whenever you have a method that is specific to a class itself, but doesn’t need to be shared across instances of that class, you can add it as a static property of the class.

class Animal {
  constructor(name, energy) {
    this.name = name
    this.energy = energy
  }
  eat(amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }
  sleep(length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }
  play(length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }
//   Now, because we added nextToEat as a static property on the class, it lives on the Animal class itself (not its prototype) and can be //   accessed using Animal.nextToEat.
  static nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a,b) => {
      return a.energy - b.energy
    })

    return sortedByLeastEnergy[0].name
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment