Advanced JavaScript - pt 2

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

One of my favorite sites is BerkshireHathaway.com - it’s simple, effective, and has been doing its job well since it launched in 1997. Even more remarkable, over the last 20 years, there’s a good chance this site has never had a bug. Why? Because it’s all static. It’s been pretty much the same since it launched over 20 years ago. Turns out sites are pretty simple to build if you have all of your data up front. Unfortunately, most sites now days don’t. To compensate for this, we’ve invented “patterns” for handling fetching external data for our apps. Like most things, these patterns each have tradeoffs that have changed over time. In this post, we’ll break down the pros and cons of three of the most common patterns, Callbacks , Promises , and Async/Await and talk about their significance and progression from a historical context.

Let’s start with the OG of these data fetching patterns, Callbacks.

Callbacks

I’m going to assume you know exactly 0 about callbacks. If I’m assuming wrong, just scroll down a bit.

When I was first learning to program, it helped me to think about functions as machines. These machines can do anything you want them to. They can even accept input and return a value. Each machine has a button on it that you can press when you want the machine to run, ().

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

add(2,3) // 5 - Press the button, run the machine.

Whether I press the button, you press the button, or someone else presses the button doesn’t matter. Whenever the button is pressed, like it or not, the machine is going to run.

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

const me = add
const you = add
const someoneElse = add

me(2,3) // 5 - Press the button, run the machine.
you(2,3) // 5 - Press the button, run the machine.
someoneElse(2,3) // 5 - Press the button, run the machine.

In the code above we assign the add function to three different variables, me , you , and someoneElse . It’s important to note that the original add and each of the variables we created are pointing to the same spot in memory. They’re literally the exact same thing under different names. So when we invoke me , you , or someoneElse , it’s as if we’re invoking add .

Now, what if we take our add machine and pass it to another machine? Remember, it doesn’t matter who presses the () button, if it’s pressed, it’s going to run.

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

function addFive (x, addReference) {
  return addReference(x, 5) // 15 - Press the button, run the machine.
}

addFive(10, add) // 15

Your brain might have got a little weird on this one, nothing new is going on here though. Instead of “pressing the button” on add , we pass add as an argument to addFive , rename it addReference , and then we “press the button” or invoke it.

This highlights some important concepts of the JavaScript language. 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 .

Because vocabulary is important, here’s the same code with the variables re-named to match the concepts they’re demonstrating.

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

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

higherOrderFunction(10, add)

This pattern should look familiar, it’s everywhere. If you’ve ever used any of the JavaScript Array methods, you’ve used a callback. If you’ve ever used lodash, you’ve used a callback. If you’ve ever used jQuery, you’ve used a callback.

[1,2,3].map((i) => i + 5)

_.filter([1,2,3,4], (n) => n % 2 === 0 );

$('#btn').on('click', () =>
  console.log('Callbacks are everywhere')
)

In general, there are two popular use cases for callbacks. The first, and what we see in the .map and _.filter examples, is a nice abstraction over transforming one value into another. We say “Hey, here’s an array and a function. Go ahead and get me a new value based on the function I gave you”. The second, and what we see in the jQuery example, is delaying execution of a function until a particular time. “Hey, here’s this function. Go ahead and invoke it whenever the element with an id of btn is clicked.” It’s this second use case that we’re going to focus on, “delaying execution of a function until a particular time”.

Right now we’ve only looked at examples that are synchronous. As we talked about at the beginning of this post, most of the apps we build don’t have all the data they need up front. Instead, they need to fetch external data as the user interacts with the app. We’ve just seen how callbacks can be a great use case for this because, again, they allow you to “delay execution of a function until a particular time”. It doesn’t take much imagination to see how we can adapt that sentence to work with data fetching. Instead of delaying execution of a function until a particular time , we can delay execution of a function until we have the data we need . Here’s probably the most popular example of this, jQuery’s getJSON method.

// updateUI and showError are irrelevant.
// Pretend they do what they sound like.

const id = 'tylermcginnis'

$.getJSON({
  url: `https://api.github.com/users/${id}`,
  success: updateUI,
  error: showError,
})

We can’t update the UI of our app until we have the user’s data. So what do we do? We say, “Hey, here’s an object. If the request succeeds, go ahead and call success passing it the user’s data. If it doesn’t, go ahead and call error passing it the error object. You don’t need to worry about what each method does, just be sure to call them when you’re supposed to”. This is a perfect demonstration of using a callback for async requests.

At this point, we’ve learned about what callbacks are and how they can be beneficial both in synchronous and asynchronous code. What we haven’t talked yet is the dark side of callbacks. Take a look at this code below. Can you tell what’s happening?

// updateUI, showError, and getLocationURL are irrelevant.
// Pretend they do what they sound like.

const id = 'tylermcginnis'

$("#btn").on("click", () => {
  $.getJSON({
    url: `https://api.github.com/users/${id}`,
    success: (user) => {
      $.getJSON({
        url: getLocationURL(user.location.split(',')),
        success (weather) {
          updateUI({
            user,
            weather: weather.query.results
          })
        },
        error: showError,
      })
    },
    error: showError
  })
})

If it helps, you can play around with the live version here.

Notice we’ve added a few more layers of callbacks. First, we’re saying don’t run the initial AJAX request until the element with an id of btn is clicked. Once the button is clicked, we make the first request. If that request succeeds, we make a second request. If that request succeeds, we invoke the updateUI method passing it the data we got from both requests. Regardless of if you understood the code at first glance or not, objectively it’s much harder to read than the code before. This brings us to the topic of “Callback Hell”.

As humans, we naturally think sequentially. 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.

Like most solutions to software problems, a commonly prescribed approach for making “Callback Hell” easier to consume is to modularize your code.

function getUser(id, onSuccess, onFailure) {
  $.getJSON({
    url: `https://api.github.com/users/${id}`,
    success: onSuccess,
    error: onFailure
  })
}

function getWeather(user, onSuccess, onFailure) {
  $.getJSON({
    url: getLocationURL(user.location.split(',')),
    success: onSuccess,
    error: onFailure,
  })
}

$("#btn").on("click", () => {
  getUser("tylermcginnis", (user) => {
    getWeather(user, (weather) => {
      updateUI({
        user,
        weather: weather.query.results
      })
    }, showError)
  }, showError)
})

If it helps, you can play around with the live version here.

OK, the function names help us understand what’s going on, but is it objectively “better”? Not by much. We’ve put a band-aid over the readability issue of Callback Hell. The problem still exists that we naturally think sequentially and, even with the extra functions, nested callbacks break us out of that sequential way of thinking.

The next issue of callbacks has to do with inversion of control. When you write a callback, you’re assuming that the program you’re giving the callback to is responsible and will call it when (and only when) it’s supposed to. You’re essentially inverting the control of your program over to another program. When you’re dealing with libraries like jQuery, lodash, or even vanilla JavaScript, it’s safe to assume that the callback function will be invoked at the correct time with the correct arguments. However, for many third-party libraries, callback functions are the interface for how you interact with them. It’s entirely plausible that a third party library could, whether on purpose or accidentally, break how they interact with your callback.

function criticalFunction () {
  // It's critical that this function
  // gets called and with the correct
  // arguments.
}

thirdPartyLib(criticalFunction)

Since you’re not the one calling criticalFunction , 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

Have you ever been to a busy restaurant without a reservation? When this happens, the restaurant needs a way to get back in contact with you when a table opens up. Historically, they’d just take your name and yell it when your table was ready. Then, as naturally occurs, they decided to start getting fancy. One solution was, instead of taking your name, they’d take your number and text you once a table opened up. This allowed you to be out of yelling range but more importantly, it allowed them to target your phone with ads whenever they wanted. Sound familiar? It should! OK, maybe it shouldn’t. It’s a metaphor for callbacks! Giving your number to a restaurant is just like giving a callback function to a third party service. You expect the restaurant to text you when a table opens up, just like you expect the third party service to invoke your function when and how they said they would. Once your number or callback function is in their hands though, you’ve lost all control.

Thankfully, there is another solution that exists. One that, by design, allows you to keep all the control. You’ve probably even experienced it before - it’s that little buzzer thing they give you. You know, this one.

Restaurant Buzzer

If you’ve never used one before, the idea is simple. Instead of taking your name or number, they give you this device. When the device starts buzzing and glowing, your table is ready. You can still do whatever you’d like as you’re waiting for your table to open up, but now you don’t have to give up anything. In fact, it’s the exact opposite. They have to give you something. There is no inversion of control.

The buzzer will always be in one of three different states - pending , fulfilled , or rejected .

pending is the default, initial state. When they give you the buzzer, it’s in this state.

fulfilled is the state the buzzer is in when it’s flashing and your table is ready.

rejected is the state the buzzer is in when something goes wrong. Maybe the restaurant is about to close or they forgot someone rented out the restaurant for the night.

Again, the important thing to remember is that you, the receiver of the buzzer, have all the control. If the buzzer gets put into fulfilled , you can go to your table. If it gets put into fulfilled and you want to ignore it, cool, you can do that too. If it gets put into rejected , that sucks but you can go somewhere else to eat. If nothing ever happens and it stays in pending , you never get to eat but you’re not actually out anything.

Now that you’re a master of the restaurant buzzer thingy, let’s apply that knowledge to something that matters.

If giving the restaurant your number is like giving them a callback function, receiving the little buzzy thing is like receiving what’s called a “Promise”.

As always, let’s start with why . Why do Promises exist? They exist to make the complexity of making asynchronous requests more manageable. Exactly like the buzzer, a Promise can be in one of three states, pending , fulfilled or rejected . Unlike the buzzer, instead of these states representing the status of a table at a restaurant, they represent the status of an asynchronous request.

If the async request is still ongoing, the Promise will have a status of pending . If the async request was successfully completed, the Promise will change to a status of fulfilled . If the async request failed, the Promise will change to a status of rejected . The buzzer metaphor is pretty spot on, right?

Now that you understand why Promises exist and the different states they can be in, there are three more questions we need to answer.

  1. How do you create a Promise?
  2. How do you change the status of a promise?
  3. How do you listen for when the status of a promise changes?
1) How do you create a Promise?

This one is pretty straight forward. You create a new instance of Promise .

const promise = new Promise()
2) How do you change the status of a promise?

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

resolve - a function that allows you to change the status of the promise to fulfilled

reject - a function that allows you to change the status of the promise to rejected .

In the code below, we use setTimeout to wait 2 seconds and then invoke resolve . This will change the status of the promise to fulfilled .

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

We can see this change in action by logging the promise right after we create it and then again roughly 2 seconds later after resolve has been called.

Notice the promise goes from <pending> to <resolved> .

3) How do you listen for when the status of a promise changes?

In my opinion, this is the most important question. It’s cool we know how to create a promise and change its status, but that’s worthless if we don’t know how to do anything after the status changes.

One thing we haven’t talked about yet is what a promise actually is. 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 . Here’s the key. 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. What this means is that once you create a promise, you’ll pass the function you want to run if the async request is successful to .then . You’ll pass the function you want to run if the async request fails to .catch .

Let’s take a look at an example. We’ll use setTimeout again to change the status of the promise to fulfilled after two seconds (2000 milliseconds).

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

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

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

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

If you run the code above you’ll notice that roughly 2 seconds later, you’ll see “Success!” in the console. Again the reason this happens is because of two things. First, when we created the promise, we invoked resolve after ~2000 milliseconds - this changed the status of the promise to fulfilled . Second, we passed the onSuccess function to the promises’ .then method. By doing that we told the promise to invoke onSuccess when the status of the promise changed to fulfilled which it did after ~2000 milliseconds.

Now let’s pretend something bad happened and we wanted to change the status of the promise to rejected . Instead of calling resolve , we would call reject .

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

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

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

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

Now this time instead of the onSuccess function being invoked, the onError function will be invoked since we called reject .

Now that you know your way around the Promise API, let’s start looking at some real code.

Remember the last async callback example we saw earlier?

function getUser(id, onSuccess, onFailure) {
  $.getJSON({
    url: `https://api.github.com/users/${id}`,
    success: onSuccess,
    error: onFailure
  })
}

function getWeather(user, onSuccess, onFailure) {
  $.getJSON({
    url: getLocationURL(user.location.split(',')),
    success: onSuccess,
    error: onFailure,
  })
}

$("#btn").on("click", () => {
  getUser("tylermcginnis", (user) => {
    getWeather(user, (weather) => {
      updateUI({
        user,
        weather: weather.query.results
      })
    }, showError)
  }, showError)
})

Is there any way we could use the Promise API here instead of using callbacks? What if we wrap our AJAX requests inside of a promise? Then we can simply resolve or reject depending on how the request goes. Let’s start with getUser .

function getUser(id) {
  return new Promise((resolve, reject) => {
    $.getJSON({
      url: `https://api.github.com/users/${id}`,
      success: resolve,
      error: reject
    })
  })
}

Nice. Notice that the parameters of getUser have changed. Instead of receiving id , onSuccess , and onFailure , it just receives id . There’s no more need for those other two callback functions because we’re no longer inverting control. Instead, we use the Promise’s resolve and reject functions. resolve will be invoked if the request was successful, reject will be invoked if there was an error.

Next, let’s refactor getWeather . We’ll follow the same strategy here. Instead of taking in onSuccess and onFailure callback functions, we’ll use resolve and reject .

function getWeather(user) {
  return new Promise((resolve, reject) => {
    $.getJSON({
      url: getLocationURL(user.location.split(',')),
      success: resolve,
      error: reject,
    })
  })
}

Looking good. Now the last thing we need to update is our click handler. Remember, here’s the flow we want to take.

  1. Get the user’s information from the Github API.
  2. Use the user’s location to get their weather from the Yahoo Weather API.
  3. Update the UI with the user’s info and their weather.

Let’s start with #1 - getting the user’s information from the Github API.

$("#btn").on("click", () => {
  const userPromise = getUser('tylermcginnis')

  userPromise.then((user) => {

  })

  userPromise.catch(showError)
})

Notice that now instead of getUser taking in two callback functions, it returns us a promise that we can call .then and .catch on. If .then is called, it’ll be called with the user’s information. If .catch is called, it’ll be called with the error.

Next, let’s do #2 - Use the user’s location to get their weather.

$("#btn").on("click", () => {
  const userPromise = getUser('tylermcginnis')

  userPromise.then((user) => {
    const weatherPromise = getWeather(user)
    weatherPromise.then((weather) => {

    })

    weatherPromise.catch(showError)
  })

  userPromise.catch(showError)
})

Notice we follow the exact same pattern we did in #1 but now we invoke getWeather passing it the user object we got from userPromise .

Finally, #3 - Update the UI with the user’s info and their weather.

$("#btn").on("click", () => {
  const userPromise = getUser('tylermcginnis')

  userPromise.then((user) => {
    const weatherPromise = getWeather(user)
    weatherPromise.then((weather) => {
      updateUI({
        user,
        weather: weather.query.results
      })
    })

    weatherPromise.catch(showError)
  })

  userPromise.catch(showError)
})

Here’s the full code you can play around with.

Our new code is better , but there are still some improvements we can make. Before we can make those improvements though, there are two more features of promises you need to be aware of, chaining and passing arguments from resolve to then .

Chaining

Both .then and .catch will return a new promise. That seems like a small detail but it’s important because it means that promises can be chained.

In the example below, we call getPromise which returns us a promise that will resolve in at least 2000 milliseconds. From there, because .then will return a promise, we can continue to chain our .then s together until we throw a new Error which is caught by the .catch method.

function getPromise () {
  return new Promise((resolve) => {
    setTimeout(resolve, 2000)
  })
}

function logA () {
  console.log('A')
}

function logB () {
  console.log('B')
}

function logCAndThrow () {
  console.log('C')

  throw new Error()
}

function catchError () {
  console.log('Error!')
}

getPromise()
  .then(logA) // A
  .then(logB) // B
  .then(logCAndThrow) // C
  .catch(catchError) // Error!

Cool, but why is this so important? Remember back in the callback section we talked about one of the downfalls of callbacks being that they force you out of your natural, sequential way of thinking. When you chain promises together, it doesn’t force you out of that natural way of thinking because chained promises are sequential. getPromise runs then logA runs then logB runs then... .

Just so you can see one more example, here’s a common use case when you use the fetch API. fetch will return you a promise that will resolve with the HTTP response. To get the actual JSON, you’ll need to call .json . Because of chaining, we can think about this in a sequential manner.

fetch('/api/user.json')
  .then((response) => response.json())
  .then((user) => {
    // user is now ready to go.
  })

Now that we know about chaining, let’s refactor our getUser / getWeather code from earlier to use it.

function getUser(id) {
  return new Promise((resolve, reject) => {
    $.getJSON({
      url: `https://api.github.com/users/${id}`,
      success: resolve,
      error: reject
    })
  })
}

function getWeather(user) {
  return new Promise((resolve, reject) => {
    $.getJSON({
      url: getLocationURL(user.location.split(',')),
      success: resolve,
      error: reject,
    })
  })
}

$("#btn").on("click", () => {
  getUser("tylermcginnis")
    .then(getWeather)
    .then((weather) => {
      // We need both the user and the weather here.
      // Right now we just have the weather
      updateUI() // ????
    })
    .catch(showError)
})

It looks much better, but now we’re running into an issue. Can you spot it? In the second .then we want to call updateUI . The problem is we need to pass updateUI both the user and the weather . Currently, how we have it set up, we’re only receiving the weather , not the user . Somehow we need to figure out a way to make it so the promise that getWeather returns is resolved with both the user and the weather .

Here’s the key. resolve is just a function. Any arguments you pass to it will be passed along to the function given to .then . What that means is that inside of getWeather , if we invoke resolve ourself, we can pass to it weather and user . Then, the second .then method in our chain will receive both user and weather as an argument.

function getWeather(user) {
  return new Promise((resolve, reject) => {
    $.getJSON({
      url: getLocationURL(user.location.split(',')),
      success(weather) {
        resolve({ user, weather: weather.query.results })
      },
      error: reject,
    })
  })
}

$("#btn").on("click", () => {
  getUser("tylermcginnis")
    .then(getWeather)
    .then((data) => {
      // Now, data is an object with a
      // "weather" property and a "user" property.

      updateUI(data)
    })
    .catch(showError)
})

You can play around with the final code here

It’s in our click handler where you really see the power of promises shine compared to callbacks.

// Callbacks 🚫
getUser("tylermcginnis", (user) => {
  getWeather(user, (weather) => {
    updateUI({
      user,
      weather: weather.query.results
    })
  }, showError)
}, showError)


// Promises ✅
getUser("tylermcginnis")
  .then(getWeather)
  .then((data) => updateUI(data))
  .catch(showError);

Following that logic feels natural because it’s how we’re used to thinking, sequentially. getUser then getWeather then update the UI with the data .

Now it’s clear that promises drastically increase the readability of our asynchronous code, but is there a way we can make it even better? Assume that you were on the TC39 committee and you had all the power to add new features to the JavaScript language. What steps, if any, would you take to improve this code?

$("#btn").on("click", () => {
  getUser("tylermcginnis")
    .then(getWeather)
    .then((data) => updateUI(data))
    .catch(showError)
})

As we’ve discussed, the code reads pretty nicely. Just as our brains work, it’s in a sequential order. One issue that we did run into was that we needed to thread the data ( users ) from the first async request all the way through to the last .then . This wasn’t a big deal, but it made us change up our getWeather function to also pass along users . 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. Here’s an idea.

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

  updateUI({
    user,
    weather,
  })
})

Well, that would be nice. Our asynchronous code looks exactly like our synchronous code. There’s no extra steps our brain needs to take because we’re already very familiar with this way of thinking. Sadly, this obviously won’t work. As you know, if we were to run the code above, user and weather would both just be promises since that’s what getUser and getWeather return. But remember, we’re on TC39. We have all the power to add any feature to the language we want. As is, this code would be really tricky to make work. We’d have to somehow teach the JavaScript engine to know the difference between asynchronous function invocations and regular, synchronous function invocations on the fly. Let’s add a few keywords to our code to make it easier on the engine.

First, let’s add a keyword to the main function itself. This could clue the engine to the fact that inside of this function, we’re going to have some asynchronous function invocations. Let’s use async for this.

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

  updateUI({
    user,
    weather,
  })
})

Cool. That seems reasonable. Next let’s add another keyword to let the engine know exactly when a function being invoked is asynchronous and is going to return a promise. Let’s use await . As in, “Hey engine. This function is asynchronous and returns a promise. Instead of continuing on like you typically do, go ahead and ‘await’ the eventual value of the promise and return it before continuing”. With both of our new async and await keywords in play, our new code will look like this.

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

  updateUI({
    user,
    weather,
  })
})

Pretty slick. We’ve invented a reasonable way to have our asynchronous code look and behave as if it were synchronous. Now the next step is to actually convince someone on TC39 that this is a good idea. Lucky for us, as you probably guessed by now, we don’t need to do any convincing because this feature is already part of JavaScript and it’s called Async/Await .

Don’t believe me? Here’s our live code now that we’ve added Async/Await to it. Feel free to play around with it.

async functions return a promise

Now that you’ve seen the benefit of Async/Await, let’s discuss some smaller details that are important to know. First, anytime you add async to a function, that function is going to implicitly return a promise.

async function getPromise(){}

const promise = getPromise()

Even though getPromise is literally empty, it’ll still return a promise since it was an async function.

If the async function returns a value, that value will also get wrapped in a promise. That means you’ll have to use .then to access it.

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

add(2,3).then((result) => {
  console.log(result) // 5
})

await without async is bad

If you try to use the await keyword inside of a function that isn’t async , you’ll get an error.

$("#btn").on("click", () => {
  const user = await getUser('tylermcginnis') // SyntaxError: await is a reserved word
  const weather = await getWeather(user.location) // SyntaxError: await is a reserved word

  updateUI({
    user,
    weather,
  })
})

Here’s how I think about it. When you add async to a function it does two things. It makes it so the function itself returns (or wraps what gets returned in) a promise and makes it so you can use await inside of it.

Error Handling

You may have noticed we cheated a little bit. In our original code we had a way to catch any errors using .catch . When we switched to Async/Await, we removed that code. With Async/Await, the most common approach is to wrap your code in a try/catch block to be able to catch the error.

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

    updateUI({
      user,
      weather,
    })
  } catch (e) {
    showError(e)
  }
})

Beginner’s Guide to JavaScript’s Prototype

You can’t get very far in JavaScript without dealing with objects. They’re foundational to almost every aspect of the JavaScript programming language. In fact, learning how to create objects is probably one of the first things you studied when you were starting out. With that said, in order to most effectively learn about prototypes in JavaScript, we’re going to channel our inner Jr. developer and go back to the basics.

Objects are key/value pairs. The most common way to create an object is with curly braces {} and you add properties and methods to an object using dot notation.

let animal = {}
animal.name = 'Leo'
animal.energy = 10

animal.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

animal.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

animal.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

Simple. Now odds are in our application we’ll need to create more than one animal. 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 animal. We’ll call this pattern Functional Instantiation and we’ll call the function itself a “constructor function” since it’s responsible for “constructing” a new object.

Functional Instantiation

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy

  animal.eat = function (amount) {
    console.log(`${this.name} is eating.`)
    this.energy += amount
  }

  animal.sleep = function (length) {
    console.log(`${this.name} is sleeping.`)
    this.energy += length
  }

  animal.play = function (length) {
    console.log(`${this.name} is playing.`)
    this.energy -= length
  }

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

"I thought this was an Advanced JavaScript course...?" - Your brain It is. We’ll get there.

Now whenever we want to create a new animal (or more broadly speaking a new “instance”), all we have to do is invoke our Animal function, passing it the animal’s name and energy level. This works great and it’s incredibly simple. However, can you spot any weaknesses with this pattern? The biggest and the one we’ll attempt to solve has to do with the three methods - eat , sleep , and play . Each of those methods are not only dynamic, but they’re also completely generic. What that means is that there’s no reason to re-create those methods as we’re currently doing whenever we create a new animal. We’re just wasting memory and making each animal object bigger than it needs to be. Can you think of a solution? What if instead of re-creating those methods every time we create a new animal, we move them to their own object then we can have each animal reference that object? We can call this pattern Functional Instantiation with Shared Methods , wordy but descriptive.

Functional Instantiation with Shared Methods

const animalMethods = {
  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
  }
}

function Animal (name, energy) {
  let animal = {}
  animal.name = name
  animal.energy = energy
  animal.eat = animalMethods.eat
  animal.sleep = animalMethods.sleep
  animal.play = animalMethods.play

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

By moving the shared methods to their own object and referencing that object inside of our Animal function, we’ve now solved the problem of memory waste and overly large animal objects.

Object.create

Let’s improve our example once again by using Object.create . Simply put, 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. That was a lot of words. Let’s see some code.

const parent = {
  name: 'Stacey',
  age: 35,
  heritage: 'Irish'
}

const child = Object.create(parent)
child.name = 'Ryan'
child.age = 7

console.log(child.name) // Ryan
console.log(child.age) // 7
console.log(child.heritage) // Irish

So in the example above, because child was created with Object.create(parent) , whenever there’s a failed property lookup on child , JavaScript will delegate that look up to the parent object. What that means is that even though child doesn’t have a heritage property, parent does so when you log child.heritage you’ll get the parent 's heritage which was Irish .

Now with Object.create in our tool shed, how can we use it in order to simplify our Animal code from earlier? Well, instead of adding all the shared methods to the animal one by one like we’re doing now, we can use Object.create to delegate to the animalMethods object instead. To sound really smart, let’s call this one Functional Instantiation with Shared Methods and Object.create :upside_down_face:

Functional Instantiation with Shared Methods and Object.create

const animalMethods = {
  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
  }
}

function Animal (name, energy) {
  let animal = Object.create(animalMethods)
  animal.name = name
  animal.energy = energy

  return animal
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

leo.eat(10)
snoop.play(5)

:chart_with_upwards_trend: So now when we call leo.eat , JavaScript will look for the eat method on the leo object. That lookup will fail, then, because of Object.create, it’ll delegate to the animalMethods object which is where it’ll find eat .

So far, so good. There are still some improvements we can make though. It seems just a tad “hacky” to have to manage a separate object ( animalMethods ) 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 the whole reason you’re here - prototype .

So what exactly is prototype in JavaScript? Well, simply put, every function in JavaScript has a prototype property that references an object. Anticlimactic, right? Test it out for yourself.

function doThing () {}
console.log(doThing.prototype) // {}

What if instead of creating a separate object to manage our methods (like we’re doing with animalMethods ), we just put each of those methods on the Animal function’s prototype? Then all we would have to do is instead of using Object.create to delegate to animalMethods , we could use it to delegate to Animal.prototype . We’ll call this pattern Prototypal Instantiation .

Prototypal Instantiation

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype)
  animal.name = name
  animal.energy = energy

  return animal
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = Animal('Leo', 7)
const snoop = Animal('Snoop', 10)

leo.eat(10)
snoop.play(5)

:clap::clap::clap: Hopefully, you just had a big “aha” moment. Again, prototype is just a property that every function in JavaScript has and, as we saw above, it allows us to share methods across all instances of a function. All our functionality is still the same but now instead of having to manage a separate object for all the methods, we can just use another object that comes built into the Animal function itself, Animal.prototype .

Let’s. Go. Deeper.

At this point we know three things:

  1. How to create a constructor function.
  2. How to add methods to the constructor function’s prototype.
  3. How to use Object.create to delegate failed lookups to the function’s prototype.

Those three tasks seem pretty foundational to any programming language. Is JavaScript really that bad that there’s no easier, “built-in” way to accomplish the same thing? As you can probably guess at this point there is, and it’s by using the new keyword.

What’s nice about the slow, methodical approach we took to get here is you’ll now have a deep understanding of exactly what the new keyword in JavaScript is doing under the hood.

Looking back at our Animal constructor, the two most important parts were creating the object and returning it. Without creating the object with Object.create , we wouldn’t be able to delegate to the function’s prototype on failed lookups. Without the return statement, we wouldn’t ever get back the created object.

function Animal (name, energy) {
  let animal = Object.create(Animal.prototype)
  animal.name = name
  animal.energy = energy

  return animal
}

Here’s the cool thing about new - when you invoke a function using the new keyword, those two lines are done for you implicitly (“under the hood”) and the object that is created is called this .

Using comments to show what happens under the hood and assuming the Animal constructor is called with the new keyword, it can be re-written as this.

function Animal (name, energy) {
  // const this = Object.create(Animal.prototype)

  this.name = name
  this.energy = energy

  // return this
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

and without the “under the hood” comments

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

Again the reason this works and that the this object is created for us is because we called the constructor function with the new keyword. If you leave off new when you invoke the function, that this object never gets created nor does it get implicitly returned. We can see the issue with this in the example below.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)
console.log(leo) // undefined

The name for this pattern is Pseudoclassical Instantiation .

If JavaScript isn’t your first programming language, you might be getting a little restless.

“WTF this dude just re-created a crappier version of a Class” - You

For those unfamiliar, a Class allows you to create a blueprint for an object. Then whenever you create an instance of that Class, you get an object with the properties and methods defined in the blueprint.

Sound familiar? That’s basically what we did with our Animal constructor function above. However, instead of using the class keyword, we just used a regular old JavaScript function to re-create the same functionality. Granted, it took a little extra work as well as some knowledge about what happens “under the hood” of JavaScript but the results are the same.

Here’s the good news. JavaScript isn’t a dead language. It’s constantly being improved and added to by the TC-39 committee. What that means is that even though the initial version of JavaScript didn’t support classes, there’s no reason they can’t be added to the official specification. In fact, that’s exactly what the TC-39 committee did. In 2015, EcmaScript (the official JavaScript specification) 6 was released with support for Classes and the class keyword. Let’s see how our Animal constructor function above would look like with the new class syntax.

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

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

Pretty clean, right?

So if this is the new way to create classes, why did we spend so much time going over the old way? The reason for that is because the new way (with the class keyword) is primarily just “syntactical sugar” over the existing way we’ve called the pseudo-classical pattern. In order to fully understand the convenience syntax of ES6 classes, you first must understand the pseudo-classical pattern.

At this point we’ve covered the fundamentals of JavaScript’s prototype. The rest of this post will be dedicated to understanding other “good to know” topics related to it. In another post, we’ll look at how we can take these fundamentals and use them to understand how inheritance works in JavaScript.

Array Methods

We talked in depth above about how if you want to share methods across instances of a class, you should stick those methods on the class’ (or function’s) prototype. We can see this same pattern demonstrated if we look at the Array class. Historically you’ve probably created your arrays like this

const friends = []

Turns out that’s just sugar over creating a new instance of the Array class.

const friendsWithSugar = []

const friendsWithoutSugar = new Array()

One thing you might have never thought about is how does every instance of an array have all of those built-in methods ( splice , slice , pop , etc)?

Well as you now know, it’s because those methods live on Array.prototype and when you create a new instance of Array , you use the new keyword which sets up that delegation to Array.prototype on failed lookups.

We can see all the array’s methods by simply logging Array.prototype .

console.log(Array.prototype)

/*
  concat: ƒn concat()
  constructor: ƒn Array()
  copyWithin: ƒn copyWithin()
  entries: ƒn entries()
  every: ƒn every()
  fill: ƒn fill()
  filter: ƒn filter()
  find: ƒn find()
  findIndex: ƒn findIndex()
  forEach: ƒn forEach()
  includes: ƒn includes()
  indexOf: ƒn indexOf()
  join: ƒn join()
  keys: ƒn keys()
  lastIndexOf: ƒn lastIndexOf()
  length: 0n
  map: ƒn map()
  pop: ƒn pop()
  push: ƒn push()
  reduce: ƒn reduce()
  reduceRight: ƒn reduceRight()
  reverse: ƒn reverse()
  shift: ƒn shift()
  slice: ƒn slice()
  some: ƒn some()
  sort: ƒn sort()
  splice: ƒn splice()
  toLocaleString: ƒn toLocaleString()
  toString: ƒn toString()
  unshift: ƒn unshift()
  values: ƒn values()
*/

The exact same logic exists for Objects as well. All objects will delegate to Object.prototype on failed lookups which is why all objects have methods like toString and hasOwnProperty .

Static Methods

Up until this point we’ve covered the why and how of sharing methods between instances of a Class. However, what if we had a method that was important to the Class, but didn’t need to be shared across instances? For example, what if we had a function that took in an array of Animal instances and determined which one needed to be fed next? We’ll call it nextToEat .

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

It doesn’t make sense to have nextToEat live on Animal.prototype since we don’t want to share it amongst all instances. Instead, we can think of it as more of a helper method. So if nextToEat shouldn’t live on Animal.prototype , where should we put it? Well, the obvious answer is we could just stick nextToEat in the same scope as our Animal class then reference it when we need it as we normally would.

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

function nextToEat (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(nextToEat([leo, snoop])) // Leo

Now this works, but there’s a better way.

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
  }
  static nextToEat(animals) {
    const sortedByLeastEnergy = animals.sort((a,b) => {
      return a.energy - b.energy
    })

    return sortedByLeastEnergy[0].name
  }
}

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 .

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo

Because we’ve followed a similar pattern throughout this post, let’s take a look at how we would accomplish this same thing using ES5. In the example above we saw how using the static keyword would put the method directly onto the class itself. With ES5, this same pattern is as simple as just manually adding the method to the function object.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

Animal.nextToEat = function (animals) {
  const sortedByLeastEnergy = animals.sort((a,b) => {
    return a.energy - b.energy
  })

  return sortedByLeastEnergy[0].name
}

const leo = new Animal('Leo', 7)
const snoop = new Animal('Snoop', 10)

console.log(Animal.nextToEat([leo, snoop])) // Leo

Getting the prototype of an object

Regardless of whichever pattern you used to create an object, getting that object’s prototype can be accomplished using the Object.getPrototypeOf method.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
const prototype = Object.getPrototypeOf(leo)

console.log(prototype)
// {constructor: ƒ, eat: ƒ, sleep: ƒ, play: ƒ}

prototype === Animal.prototype // true

There are two important takeaways from the code above.

First, you’ll notice that proto is an object with 4 methods, constructor , eat , sleep , and play . That makes sense. We used getPrototypeOf passing in the instance, leo getting back that instances’ prototype, which is where all of our methods are living. This tells us one more thing about prototype as well that we haven’t talked about yet. By default, the prototype object will have a constructor property which points to the original function or the class that the instance was created from. What this also means is that because JavaScript puts a constructor property on the prototype by default, any instances will be able to access their constructor via instance.constructor .

The second important takeaway from above is that Object.getPrototypeOf(leo) === Animal.prototype . That makes sense as well. The Animal constructor function has a prototype property where we can share methods across all instances and getPrototypeOf allows us to see the prototype of the instance itself.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function

To tie in what we talked about earlier with Object.create , the reason this works is because any instances of Animal are going to delegate to Animal.prototype on failed lookups. So when you try to access leo.constructor , leo doesn’t have a constructor property so it will delegate that lookup to Animal.prototype which indeed does have a constructor property. If this paragraph didn’t make sense, go back and read about Object.create above.

You may have seen proto used before to get an instances’ prototype. That’s a relic of the past. Instead, use Object.getPrototypeOf(instance) as we saw above.

Determining if a property lives on the prototype

There are certain cases where you need to know if a property lives on the instance itself or if it lives on the prototype the object delegates to. We can see this in action by looping over our leo object we’ve been creating. Let’s say the goal was the loop over leo and log all of its keys and values. Using a for in loop, that would probably look like this.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)

for(let key in leo) {
  console.log(`Key: ${key}. Value: ${leo[key]}`)
}

What would you expect to see? Most likely, it was something like this -

Key: name. Value: Leo
Key: energy. Value: 7

However, what you saw if you ran the code was this -

Key: name. Value: Leo
Key: energy. Value: 7
Key: eat. Value: function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}
Key: sleep. Value: function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}
Key: play. Value: function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

Why is that? Well, a for in loop is going to loop over all of the enumerable properties on both the object itself as well as the prototype it delegates to. Because by default any property you add to the function’s prototype is enumerable, we see not only name and energy , but we also see all the methods on the prototype - eat , sleep , and play . To fix this, we either need to specify that all of the prototype methods are non-enumerable or we need a way to only console.log if the property is on the leo object itself and not the prototype that leo delegates to on failed lookups. This is where hasOwnProperty can help us out.

hasOwnProperty is a property on every object that returns a boolean indicating whether the object has the specified property as its own property rather than on the prototype the object delegates to. That’s exactly what we need. Now with this new knowledge, we can modify our code to take advantage of hasOwnProperty inside of our for in loop.

...

const leo = new Animal('Leo', 7)

for(let key in leo) {
  if (leo.hasOwnProperty(key)) {
    console.log(`Key: ${key}. Value: ${leo[key]}`)
  }
}

And now what we see are only the properties that are on the leo object itself rather than on the prototype leo delegates to as well.

Key: name. Value: Leo
Key: energy. Value: 7

If you’re still a tad confused about hasOwnProperty , here is some code that may clear it up.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)

leo.hasOwnProperty('name') // true
leo.hasOwnProperty('energy') // true
leo.hasOwnProperty('eat') // false
leo.hasOwnProperty('sleep') // false
leo.hasOwnProperty('play') // false

Check if an object is an instance of a Class

Sometimes you want to know whether an object is an instance of a specific class. To do this, you can use the instanceof operator. The use case is pretty straight forward but the actual syntax is a bit weird if you’ve never seen it before. It works like this

object instanceof Class

The statement above will return true if object is an instance of Class and false if it isn’t. Going back to our Animal example we’d have something like this.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

function User () {}

const leo = new Animal('Leo', 7)

leo instanceof Animal // true
leo instanceof User // false

The way that instanceof works is it checks for the presence of constructor.prototype in the object’s prototype chain. In the example above, leo instanceof Animal is true because Object.getPrototypeOf(leo) === Animal.prototype . In addition, leo instanceof User is false because Object.getPrototypeOf(leo) !== User.prototype .

Creating new agnostic constructor functions

Can you spot the error in the code below?

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = Animal('Leo', 7)

Even seasoned JavaScript developers will sometimes get tripped up on the example above. Because we’re using the pseudoclassical pattern that we learned about earlier, when the Animal constructor function is invoked, we need to make sure we invoke it with the new keyword. If we don’t, then the this keyword won’t be created and it also won’t be implicitly returned.

As a refresher, the commented out lines are what happens behind the scenes when you use the new keyword on a function.

function Animal (name, energy) {
  // const this = Object.create(Animal.prototype)

  this.name = name
  this.energy = energy

  // return this
}

This seems like too important of a detail to leave up to other developers to remember. Assuming we’re working on a team with other developers, is there a way we could ensure that our Animal constructor is always invoked with the new keyword? Turns out there is and it’s by using the instanceof operator we learned about previously.

If the constructor was called with the new keyword, then this inside of the body of the constructor will be an instanceof the constructor function itself. That was a lot of big words. Here’s some code.

function Animal (name, energy) {
  if (this instanceof Animal === false) {
    console.warn('Forgot to call Animal with the new keyword')
  }

  this.name = name
  this.energy = energy
}

Now instead of just logging a warning to the consumer of the function, what if we re-invoke the function but with the new keyword this time?

function Animal (name, energy) {
  if (this instanceof Animal === false) {
    return new Animal(name, energy)
  }

  this.name = name
  this.energy = energy
}

Now regardless of if Animal is invoked with the new keyword, it’ll still work properly.

Re-creating Object.create

Throughout this post, we’ve relied heavily upon Object.create in order to create objects which delegate to the constructor function’s prototype. At this point, you should know how to use Object.create inside of your code but one thing that you might not have thought of is how Object.create actually works under the hood. In order for you to really understand how Object.create works, we’re going to re-create it ourselves. First, what do we know about how Object.create works?

  1. It takes in an argument that is an object.
  2. It creates an object that delegates to the argument object on failed lookups.
  3. It returns the new created object.

Let’s start off with #1.

Object.create = function (objToDelegateTo) {

}

Simple enough.

Now #2 - we need to create an object that will delegate to the argument object on failed lookups. This one is a little more tricky. To do this, we’ll use our knowledge of how the new keyword and prototypes work in JavaScript. First, inside the body of our Object.create implementation, we’ll create an empty function. Then, we’ll set the prototype of that empty function equal to the argument object. Then, in order to create a new object, we’ll invoke our empty function using the new keyword. If we return that newly created object, that’ll finish #3 as well.

Object.create = function (objToDelegateTo) {
  function Fn(){}
  Fn.prototype = objToDelegateTo
  return new Fn()
}

Wild. Let’s walk through it.

When we create a new function, Fn in the code above, it comes with a prototype property. When we invoke it with the new keyword, we know what we’ll get back is an object that will delegate to the function’s prototype on failed lookups. If we override the function’s prototype, then we can decide which object to delegate to on failed lookups. So in our example above, we override Fn 's prototype with the object that was passed in when Object.create was invoked which we call objToDelegateTo .

Note that we’re only supporting a single argument to Object.create. The official implementation also supports a second, optional argument which allows you to add more properties to the created object.

Arrow Functions

Arrow functions don’t have their own this keyword. As a result, arrow functions can’t be constructor functions and if you try to invoke an arrow function with the new keyword, it’ll throw an error.

const Animal = () => {}

const leo = new Animal() // Error: Animal is not a constructor

Also, because we demonstrated above that the pseudo-classical pattern can’t be used with arrow functions, arrow functions also don’t have a prototype property.

const Animal = () => {}
console.log(Animal.prototype) // undefined

JavaScript Private and Public Class Fields

My favorite part of the JavaScript community is that everyone seems to always be asking “why?” . Why do we do things the way we do them? Generally, the answer to that question is full of reason and historical context. But sometimes, the answer tends to be more simple - “because that’s what we’ve always done.”

In the previous post we learned how to create JavaScript classes in both ES5 as well as ES6. We also discussed how to add state to the instances of those classes via the constructor as well as how to share methods across instances via the classes’ prototype. Here’s a simple Player class which encompasses everything we discussed in regards to ES6 classes.

class Player {
  constructor() {
    this.points = 0
    this.assists = 0
    this.rebounds = 0
    this.steals = 0
  }
  addPoints(amount) {
    this.points += amount
  }
  addAssist() {
    this.assists++
  }
  addRebound() {
    this.rebounds++
  }
  addSteal() {
    this.steals++
  }
}

Looking at that code, is there any way we can make it a little more intuitive? The methods are fine, those come pretty naturally. What about the constructor? What even is a constructor and why do we have to define instance values there? Now, there are answers to those questions but why can’t we just add state to our instances just like we did with the methods? Something like this

class Player {
  points = 0
  assists = 0
  rebounds = 0
  steals = 0
  addPoints(amount) {
    this.points += amount
  }
  addAssist() {
    this.assists++
  }
  addRebound() {
    this.rebounds++
  }
  addSteal() {
    this.steals++
  }
}

It turns out this is the foundation for the Class Fields Declaration proposal which is currently at Stage 3 in the TC-39 process. This proposal will allow you to add instance properties directly as a property on the class without having to use the constructor method. Pretty slick, but where this proposal really shines is if we look at some React code. Here’s a typical React component. It has local state, some methods, and a few static properties being added to the class.

class PlayerInput extends Component {
  constructor(props) {
    super(props)
    this.state = {
      username: ''
    }

    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}

PlayerInput.propTypes = {
  id: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  onSubmit: PropTypes.func.isRequired,
}

PlayerInput.defaultProps = {
  label: 'Username',
}

Let’s see how the new Class Fields proposal improves the code above First, we can take our state variable out of the constructor and define it directly as a property (or “field”) on the class.

class PlayerInput extends Component {
  state = {
    username: ''
  }
  constructor(props) {
    super(props)

    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}

PlayerInput.propTypes = {
  id: PropTypes.string.isRequired,
  label: PropTypes.string.isRequired,
  onSubmit: PropTypes.func.isRequired,
}

PlayerInput.defaultProps = {
  label: 'Username',
}

Cool, but nothing to get too excited over. Let’s keep going. In the previous post, we talked about how you can add static methods to the class itself by using the static keyword. However, according to the ES6 class specification, this only works with methods, not values. That’s why in the code above we have to add propTypes and defaultProps to PlayerInput after we define it and not in the class body. Again, why can’t those go directly on the class body just as a static method would? Well, the good news is this is encompassed in the Class Fields proposal as well. So now instead of just defining static methods in the class body, you can also define static values. What that means for our code is we can move propTypes and defaultProps up into the class definition.

class PlayerInput extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    onSubmit: PropTypes.func.isRequired,
  }
  static defaultProps = {
    label: 'Username'
  }
  state = {
    username: ''
  }
  constructor(props) {
    super(props)

    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(event) {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}

Much better, but we still have that ugly constructor method and super invocation. Again, the reason we need the constructor right now is in order to bind the handleChange method to the correct context. If we could figure out another way to make sure handleChange was always invoked in the correct context, we could get rid of the constructor altogether.

If you’ve used arrow functions before, you know that they don’t have their own this keyword. Instead, the this keyword is bound lexically . That’s a fancy way of saying when you use the this keyword inside of an arrow function, things behave how you’d expect them to. Taking that knowledge and combining it with the “Class Fields” proposal, what if we swapped out the handleChange method for an arrow function? Seems a little weird but by doing this we’d get rid of the .bind issue altogether since, again, arrow functions bind this lexically.

class PlayerInput extends Component {
  static propTypes = {
    id: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    onSubmit: PropTypes.func.isRequired,
  }
  static defaultProps = {
    label: 'Username'
  }
  state = {
    username: ''
  }
  handleChange = (event) => {
    this.setState({
      username: event.target.value
    })
  }
  render() {
    ...
  }
}

Well, would you look at that? That’s much better than the original class we started with and it’s all thanks to the Class Fields proposal which will be part of the official EcmaScript specification soon.

From a developer experience standpoint, Class Fields are a clear win. However, there are some downsides to them that are rarely talked about. In the last post, we talked about how ES6 classes are just sugar over what we called the “pseudo-classical” pattern. Meaning, when you add a method to a class, that’s really like adding a method to the function’s prototype.

class Animal {
  eat() {}
}

// Is equivalent to

function Animal () {}
Animal.prototype.eat = function () {}

This is performant because eat is defined once and shared across all instances of the class. What does this have to do with Class Fields? Well, as we saw above, Class Fields are added to the instance. This means that, when using Class Fields, for each instance we create we’ll be re-creating all of the methods in memory.

class Animal {
  eat() {}
  sleep = () => {}
}

// Is equivalent to

function Animal () {
  this.sleep = function () {}
}

Animal.prototype.eat = function () {}

Notice how sleep gets put on the instance and not on Animal.prototype . Is this a bad thing? Well, it can be. Making broad statements about performance without measuring is generally a bad idea. The question you need to answer in your application is if the developer experience you gain from Class Fields outweighs the potential performance hit.

If you want to use any of what we’ve talked about so far in your app, you’ll need to use the babel-plugin-transform-class-properties plugin.

Private Fields

Another aspect of the Class Fields proposal are “private fields”. Sometimes when you’re building a class, you want to have private values that aren’t exposed to the outside world. Historically in JavaScript, because we’ve lacked the ability to have truly private values, we’ve marked them with an underscore.

class Car {
  _milesDriven = 0
  drive(distance) {
    this._milesDriven += distance
  }
  getMilesDriven() {
    return this._milesDriven
  }
}

In the example above, we’re relying on the consumer of the Car class to get the car’s mileage by invoking the getMilesDriven method. However, because there’s really nothing making _milesDriven private, any instance can access it.

const tesla = new Car()
tesla.drive(10)
console.log(tesla._milesDriven)

There are fancy (hacky) ways around this problem using WeakMaps, but it would be nice if a simpler solution existed. Again, the Class Fields proposal is coming to our rescue. According to the proposal, you can create a private field using a # . Yes, you read that right, # . Let’s take a look at what that does to our code,

class Car {
  #milesDriven = 0
  drive(distance) {
    this.#milesDriven += distance
  }
  getMilesDriven() {
    return this.#milesDriven
  }
}

and we can go one step further with the shorthand syntax

class Car {
  #milesDriven = 0
  drive(distance) {
    #milesDriven += distance
  }
  getMilesDriven() {
    return #milesDriven
  }
}

const tesla = new Car()
tesla.drive(10)
tesla.getMilesDriven() // 10
tesla.#milesDriven // Invalid

If you’re interested in more of the details/decisions behind private fields, there’s a great write-up here.

There’s currently a PR to add private fields to Babel so you can use them in your apps.

JavaScript Inheritance and the Prototype Chain

Previously we learned how to create an Animal class both in ES5 as well as in ES6. We also learned how to share methods across those classes using JavaScript’s prototype. To review, here’s the code we saw in an earlier post.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
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
  }
}

const leo = new Animal('Leo', 7)

Now let’s say we wanted to start making individual classes for specific animals. For example, what if we wanted to start making a bunch of dog instances. What properties and methods will these dogs have? Well, similar to our Animal class, we could give each dog a name , an energy level, and the ability to eat , sleep , and play . Unique to our Dog class, we could also give them a breed property as well as the ability to bark . In ES5, our Dog class could look something like this

function Dog (name, energy, breed) {
  this.name = name
  this.energy = energy
  this.breed = breed
}

Dog.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Dog.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Dog.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

Dog.prototype.bark = function () {
  console.log('Woof-Woof!')
  this.energy -= .1
}

const charlie = new Dog('Charlie', 10, 'Goldendoodle')

Alright, well… we just recreated the Animal class and added a few new properties to it. If we wanted to create another animal, say a Cat , at this point we’d again have to create a Cat class, duplicate all the common logic located in the Animal class to it, then add on Cat specific properties just like we did with the Dog class. In fact, we’d have to do this for each different type of animal we created.

function Dog (name, energy, breed) {}

function Cat (name, energy, declawed) {}

function Giraffe (name, energy, height) {}

function Monkey (name, energy, domesticated) {}

This work, but it seems wasteful. The Animal class is the perfect base class. What that means is that it has all the properties that each one of our animals has in common. Whether we’re creating a dog, cat, giraffe, or monkey, all of them will have a name , energy level, and the ability to eat , sleep , and play . With that said, is there a way we can utilize the Animal class whenever we create the individual classes for each different animal? Let’s try it out. I’ll paste the Animal class again below for easy reference.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

function Dog (name, energy, breed) {}

What are some things we know about the Dog constructor function above?

First, we know it takes 3 arguments, name , energy , and breed .

Second, we know it’s going to be called with the new keyword so we’ll have a this object.

And third, we know we need to utilize the Animal function so that any instance of dog will have a name , energy level, and be able to eat , sleep , and play .

It’s the third one that’s the tricky one. The way you “utilize” a function is by calling it. So we know that inside of Dog , we want to call Animal . What we need to figure out though is how we can invoke Animal in the context of Dog . What that means it that we want to call Animal with the this keyword from Dog . If we do that correctly, then this inside of the Dog function will have all the properties of Animal ( name , energy ). If you remember from a previous section, every function in JavaScript has a .call method on it.

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

This sounds like exactly what we need. We want to invoke Animal in the context of Dog .

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

const charlie = new Dog('Charlie', 10, 'Goldendoodle')

charlie.name // Charlie
charlie.energy // 10
charlie.breed // Goldendoodle

Solid, we’re half-way there. You’ll notice in the code above that because of this line Animal.call(this, name, energy) , every instance of Dog will now have a name and energy property. Again, the reason for that is because it’s as if we ran the Animal function with the this keyword generated from Dog . Then after we added a name and energy property to this , we also added a breed property just as we normally would.

Remember the goal here is to have each instance of Dog have not only all the properties of Animal , but also all the methods as well. If you run the code above, you’ll notice that if you try to run charlie.eat(10) you’ll get an error. Currently every instance of Dog will have the properties of Animal ( name and energy ), but we haven’t done anything to make sure that they also have the methods ( play , eat , sleep ).

Let’s think about how we can solve this. We know that all the Animal 's methods are located on Animal.prototype . What that means is we somehow want to make sure that all instances of Dog will have access to the methods on Animal.prototype . What if we used our good friend Object.create here? If you’ll remember, Object.create allows you to create an object which will delegate to another object on failed lookups. So in our case, the object we want to create is going to be Dog 's prototype and the object we want to delegate to on failed lookups is Animal.prototype .

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)

Now, whenever there’s a failed lookup on an instance of Dog , JavaScript will delegate that lookup to Animal.prototype . If this is still a little fuzzy, re-read A Beginner’s Guide to JavaScript’s Prototype where we talk all about Object.create and JavaScript’s prototype.

Let’s look at the full code together then we’ll walk through what happens.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)

Now we’ve created our base class ( Animal ) as well as our subclass ( Dog ), let’s see what it looks like under the hood when we create an instance of Dog .

const charlie = new Dog('Charlie', 10, 'Goldendoodle')

charlie.name // Charlie
charlie.energy // 10
charlie.breed // Goldendoodle

Nothing fancy so far, but let’s look at what happens when we invoke a method located on Animal .

charlie.eat(10)

/*
1) JavaScript checks if charlie has an eat property - it doesn't.
2) JavaScript then checks if Dog.prototype has an eat property
    - it doesn't.
3) JavaScript then checks if Animal.prototype has an eat property
    - it does so it calls it.
*/

The reason Dog.prototype gets checked is because when we created a new instance of Dog , we used the new keyword. Under the hood, the this object that was created for us delegates to Dog.prototype (seen in comments below).

function Dog (name, energy, breed) {
  // this = Object.create(Dog.prototype)  Animal.call(this, name, energy)

  this.breed = breed
  // return this}

The reason Animal.prototype gets checked is because we overwrote Dog.prototype to delegate to Animal.prototype on failed lookups with this line

Dog.prototype = Object.create(Animal.prototype)

Now one thing we haven’t talked about is what if Dog has its own methods? Well, that’s a simple solution. Just like with Animal , if we want to share a method across all instances of that class, we add it to the function’s prototype.

...

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)

Dog.prototype.bark = function () {  console.log('Woof Woof!')  this.energy -= .1}

:ok_hand: very nice. There’s just one small addition we need to make. If you remember back to the Beginner’s Guide to JavaScript’s Prototype post, we were able to get access to the instances’ constructor function by using instance.constructor .

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

const leo = new Animal('Leo', 7)
console.log(leo.constructor) // Logs the constructor function

As explained in the previous post, “the reason this works is because any instances of Animal are going to delegate to Animal.prototype on failed lookups. So when you try to access leo.constructor , leo doesn’t have a constructor property so it will delegate that lookup to Animal.prototype which indeed does have a constructor property.”

The reason I bring this up is because in our implementation, we overwrote Dog.prototype with an object that delegates to Animal.prototype .

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.bark = function () {
  console.log('Woof Woof!')
  this.energy -= .1
}

What that means is that now, any instances of Dog which log instance.constructor are going to get the Animal constructor rather than the Dog constructor. You can see for yourself by running this code -

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)

Dog.prototype.bark = function () {
  console.log('Woof Woof!')
  this.energy -= .1
}

const charlie = new Dog('Charlie', 10, 'Goldendoodle')
console.log(charlie.constructor)

Notice it gives you the Animal constructor even though charlie is a direct instance of Dog . Again, we can walk through what’s happening here just like we did above.

const charlie = new Dog('Charlie', 10, 'Goldendoodle')
console.log(charlie.constructor)

/*
1) JavaScript checks if charlie has a constructor property - it doesn't.
2) JavaScript then checks if Dog.prototype has a constructor property
    - it doesn't because it was deleted when we overwrote Dog.prototype.
3) JavaScript then checks if Animal.prototype has a constructor property
    - it does so it logs that.
*/

How can we fix this? Well, it’s pretty simple. We can just add the correct constructor property to Dog.prototype once we overwrite it.

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)

Dog.prototype.bark = function () {
  console.log('Woof Woof!')
  this.energy -= .1
}

Dog.prototype.constructor = Dog

At this point if we wanted to make another subclass, say Cat , we’d follow the same pattern.

function Cat (name, energy, declawed) {
  Animal.call(this, name, energy)

  this.declawed = declawed
}

Cat.prototype = Object.create(Animal.prototype)
Cat.prototype.constructor = Cat

Cat.prototype.meow = function () {
  console.log('Meow!')
  this.energy -= .1
}

This concept of having a base class with subclasses that delegate to it is called inheritance and it’s a staple of Object Oriented Programming (OOP) . If you’re coming from a different programming language, odds are you’re already familiar with OOP and inheritance. Before ES6 classes, in JavaScript, inheritance was quite the task as you can see above. You need to understand now only when to use inheritance, but also a nice mix of .call , Object.create , this , and FN.prototype - all pretty advanced JS topics. Let’s see how we’d accomplish the same thing using ES6 classes though.

First, let’s review what it looks like to go from an ES5 “class” to an ES6 class using our Animal class.

function Animal (name, energy) {
  this.name = name
  this.energy = energy
}

Animal.prototype.eat = function (amount) {
  console.log(`${this.name} is eating.`)
  this.energy += amount
}

Animal.prototype.sleep = function (length) {
  console.log(`${this.name} is sleeping.`)
  this.energy += length
}

Animal.prototype.play = function (length) {
  console.log(`${this.name} is playing.`)
  this.energy -= length
}

const leo = new Animal('Leo', 7)
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
  }
}

const leo = new Animal('Leo', 7)

Now that we’ve refactored our Animal constructor function into an ES6 class, the next thing we need to do is figure out how to refactor our base class ( Dog ). The good news is it’s much more intuitive. For reference, in ES5, here’s what we had.

function Dog (name, energy, breed) {
  Animal.call(this, name, energy)

  this.breed = breed
}

Dog.prototype = Object.create(Animal.prototype)

Dog.prototype.bark = function () {
  console.log('Woof Woof!')
  this.energy -= .1
}

Dog.prototype.constructor = Dog

Before we get into inheritance, let’s refactor Dog to use an ES6 class as we learned in a previous post.

class Dog {
  constructor(name, energy, breed) {
    this.breed = breed
  }
  bark() {
    console.log('Woof Woof!')
    this.energy -= .1
  }
}

Looks great. Now, let’s figure out how to make sure that Dog inherits from Animal . The first step we need to make is a pretty straight forward one. With ES6 classes, you can extend a base class with this syntax

class Subclass extends Baseclass {}

Translated into our example, that would make our Dog class look like this

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

class Dog extends Animal {  constructor(name, energy, breed) {
    this.breed = breed
  }
  bark() {
    console.log('Woof Woof!')
    this.energy -= .1
  }
}

In ES5 in order to make sure that every instance of Dog had a name and an energy property, we used .call in order to invoke the Animal constructor function in the context of the Dog instance. Luckily for us, in ES6 it’s much more straight forward. Whenever you are extending a base class and you need to invoke that base class’ constructor function, you invoke super passing it any arguments it needs. So in our example, our Dog constructor gets refactored to look like this

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

class Dog extends Animal {
  constructor(name, energy, breed) {
    super(name, energy) // calls Animal's constructor
    this.breed = breed
  }
  bark() {
    console.log('Woof Woof!')
    this.energy -= .1
  }
}

And that’s it. No using .call , no using Object.create , no worrying about resetting constructor on the prototype - just extends the base class and make sure to call super .

What’s interesting about JavaScript is the same patterns you’ve learned these last few posts are directly baked into the language itself. Previously you learned that the reason all instances of Array have access to the array methods like pop , slice , filter , etc are because all of those methods live on Array.prototype .

console.log(Array.prototype)

/*
  concat: ƒn concat()
  constructor: ƒn Array()
  copyWithin: ƒn copyWithin()
  entries: ƒn entries()
  every: ƒn every()
  fill: ƒn fill()
  filter: ƒn filter()
  find: ƒn find()
  findIndex: ƒn findIndex()
  forEach: ƒn forEach()
  includes: ƒn includes()
  indexOf: ƒn indexOf()
  join: ƒn join()
  keys: ƒn keys()
  lastIndexOf: ƒn lastIndexOf()
  length: 0n
  map: ƒn map()
  pop: ƒn pop()
  push: ƒn push()
  reduce: ƒn reduce()
  reduceRight: ƒn reduceRight()
  reverse: ƒn reverse()
  shift: ƒn shift()
  slice: ƒn slice()
  some: ƒn some()
  sort: ƒn sort()
  splice: ƒn splice()
  toLocaleString: ƒn toLocaleString()
  toString: ƒn toString()
  unshift: ƒn unshift()
  values: ƒn values()
*/

You also learned that the reason all instances of Object have access to methods like hasOwnProperty and toString is because those methods live on Object.prototype .

console.log(Object.prototype)

/*
  constructor: ƒn Object()
  hasOwnProperty: ƒn hasOwnProperty()
  isPrototypeOf: ƒn isPrototypeOf()
  propertyIsEnumerable: ƒn propertyIsEnumerable()
  toLocaleString: ƒn toLocaleString()
  toString: ƒn toString()
  valueOf: ƒn valueOf()
*/

Here’s a challenge for you. With the list of Array methods and Object methods above, why does this code below work?

const friends = ['Mikenzi', 'Jake', 'Ean']

friends.hasOwnProperty('push') // false

If you look at Array.prototype , there isn’t a hasOwnProperty method. Well if there isn’t a hasOwnProperty method located on Array.prototype , how does the friends array in the example above have access to hasOwnProperty ? The reason for that is because the Array class extends the Object class. So in our example above, when JavaScript sees that friends doesn’t have a hasOwnProperty property, it checks if Array.prototype does. When Array.prototype doesn’t, it checks if Object.prototype does, then it invokes it. It’s the same process we’ve seen throughout this blog post.

JavaScript has two types - Primitive types and Reference types.

Primitive types are boolean , number , string , null , and undefined and are immutable. Everything else is a reference type and they all extend Object.prototype . That’s why you can add properties to functions and arrays and that’s why both functions and arrays have access to the methods located on Object.prototype .

function speak(){}
speak.woahFunctionsAreLikeObjects = true
console.log(speak.woahFunctionsAreLikeObjects) // true

const friends = ['Mikenzi', 'Jake', 'Ean']
friends.woahArraysAreLikeObjectsToo = true
console.log(friends.woahArraysAreLikeObjectsToo) // true