Day 1: Everything is (sorta) an Object in JavaScript

The first time you realize that functions can have properties in JavaScript your brain does a Gary Bernhardt "/wat?/"; because it doesn't make sense. If we were all calling APIs like string.capitalize! and array.each all the time then the object oriented nature of JS would be obvious. JS doesn't encourage this style of programming so few of us realize how objecty JS is.

The truth is, JS is a very object oriented language; its object features are just hidden behind stuff like; prototypal inheritance, this, calls, applys, and binds.

We can add properties to anything in JS. For example, we can create a function called cats and add an awesomenessLevel property to it:

const cats = () => {
  return 'cats are awesome'
}

cats.awesomenessLevel = 100
cats
Try in REPL

Of course, as we already know, objects in JS have properties too:

const animals = {}

animals.cats = {
  awesomenessLevel: 100,
}
animals
Try in REPL

Since functions are objects we can give our functions functions:

const cat = () => {
  return "Is awesome"
}

cat.talk = () => {
  return "Shut up and feed me human"
}

cat.talk()
Try in REPL

That might look a little strange but is actually how object methods (i.e classes) are implemented in JS.

Object Instances in JavaScript

Let's imagine for a moment that we wanted multiple cats and we wanted each cat to be able to say its name. How might we accomplish such a thing without using any JS OOP features like new or classes?

Let's start with the simplest example, let's define a cat object with properties for name and weight:

const Cat = {
  name: '',
  weight: 0,
}

Now we need a function that takes some properties like name and weight and returns a Cat:

const newCat = ({ name, weight }) => {
  return {
    name,
    weight,
  }
}

How do we make the cat talk? The solution is sort of obvious. To give our cat her voice we extend the cat object with a talk function:

const newCat = ({ name, weight }) => {
  const cat = {
    name,
    weight,
    talk: () => {
      return `Hey! My name is ${name}. I weigh ${weight}`
    }
  }

  return cat
}

const cat1 = newCat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
console.log(cat1.talk())
const cat2 = newCat({ name: 'Boots', weight: 'one newton' })
cat2.talk()
Try in REPL

This is pretty cool! Our cat can now totally fall in love with a handsome prince.

It works well and everyone is happy, until a few weeks later. Due to advancements in cat AI; we now need to add a lot more if statements to our talk function. We need cats with procedurally generated meows pulled from the clouds! We can no longer tolerate a constructor function with so many lines. We need to break this talk method off, into a separate place.

this to the rescue

this is one of the most annoying and confusing aspects of JavaScript. It has been the cause of countless frustrated developers, mental health problems, divorces, and interdimensional rifts opening and destroying New England supermarkets. Under most scenarios I'd recommend that you avoid JS all together, but in your case, the scientists are insisting on genetically engineering talking cats, so let's keep our head down and get to work. We can deal with the inevitable collapse of reality this is sure to cause later.

How do we split off the talk method for our cat so that it can be super long (aka AI) and yet keep our code super clean? We can write a talk method that just assumes it will have an object (represented by this), then we can pass in that object later:

function talk() {
  // lots of AI here
  return `Hey! My name is ${this.name}. I weigh ${this.weight}`
}

How do we get this into our talk method? We can use a good old staple of JS OOP called call. call takes an object and passes it into the function as this:

function talk() {
  // lots of AI here
  return `Hey! My name is ${this.name}. I weigh ${this.weight}`
}

const newCat = ({ name, weight }) => {
  const cat = {
    name,
    weight,
    talk: () => talk.call(cat)
  }

  return cat
}

const cat = newCat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
cat.talk()
Try in REPL

this can be thought of as a special property on objects. There is no reason we couldn't adopt a C oriented OOP style and simply pass in cat as an argument to talk:

function talk(cat) {
  // lots of AI here
  return `Hey! My name is ${cat.name}. I weigh ${cat.weight}`
}

It is just cleaner and makes the function more reasonable and composable to use the special this property.

We can permanently bind the talk function to the cat object by using the aptly named bind:

function talk() {
  // lots of AI here
  return `Hey! My name is ${this.name}. I weigh ${this.weight}`
}

const newCat = ({ name, weight }) => {
  const cat = {
    name,
    weight,
    talk: undefined,
  }

  cat.talk = talk.bind(cat)

  return cat
}

const cat = newCat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
cat.talk()
Try in REPL

Calling a new

It would be pretty repetitive to have to create object instances in this way. This is why JS provides the new operator which does the same thing as the above. To use new we need to define a constructor function:

function cat({ name, weight }) {
  return {
    name,
    weight
  }
}
const cat1 = new cat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
cat1
Try in REPL

A constructor function takes an object (this) and sets properties on that object. In most cases the object is created by new and this will be used; but it is also perfectly valid to return a new object from the constructor.

new is just syntatic sugar

Are numbers (and other primitives) not objects?

If you console.dir a number something interesting will happen, you will get back just a number:

const numberything = 1
console.dir(numberything) // => 1
Try in REPL

But we know that we can call the toString method on any number in JS and get back a String:

const numberything = 1
numberything.toString // => "1"
Try in REPL

So, the question is, where is the object? How are we getting methods on the number when it is a primitive? The answer Quentin, is magic. In this case our magic is wrapper objects. When we call a method on a primitive JS converts it to an object and back again. This is why when you are going to be treating a Number as an object, it is more performant to create it as one to start with. Every time you treat a primitive as an object it becomes an object. Now you know why those math libs use objects so much.

Primitives are Immutable and Objects are Mutable

A good way to think about primitives vs objects is that the former is immutable and the latter is mutable. You cant add anything to a primitive:

const numberything = 1
numberything.cats = 'are awesome' // Cannot create property 'cats' on number '1'

If we create the number as a Number object instead then we can modify it:

const numberything = new Number(1)
numberything.cats = 'cats are awesome'
numberything.cats
Try in REPL

Comparing Objects

A big difference between Primitives and Objects is how they are compared. Primitives are compared by value and objects are compared by reference. Two primitives with the same values will be equal:

const cats = 'are awesome'
const dogs = 'are awesome'
cats === dogs
Try in REPL

But if we create those strings as objects they will not be the same:

const cats = new String('are awesome')
const dogs = new String('are awesome')
cats === dogs
Try in REPL

This is because in the second example, both the strings have their references compared. If we wanted to check if their values are the same we'd need to specifically convert their values to a primitive:

const cats = new String('are awesome')
const dogs = new String('are awesome')
cats.toString() === dogs.toString() // => true
Try in REPL

A good way to understand references is by trying to re-use one and seeing what happens:

const cat1 = { name: 'Ferguson' }
const cat2 = cat1

cat2.name = 'Sweatshirt'

cat1 === cat2 // => true
cat1.name // => 'Sweatshirt'
Try in REPL

What we did is create a reference to the first cat and modify its name.

Classes

The first time you encounter prototypes you usually are just trying to make a class. You quickly learn that you can construct an object using new and that it is a lot like objects you are used to. But without a class Cat sort of syntax you can be lost as to how you wrap up static methods. You just want a class! Why is that so hard?! Why is JS so confusing?!

Learning JS can feel like trying to understand the movie Primer. Does everyone else understand this? Because I don't. I seriously don't. Where they the whole time? What about the? Okay I'm googling this. Still confused. Thanks google.

The key to understanding JS classes is to remember that a constructor function is an object too, we can add whatever we want to it. If you need static class functions; you can just add class methods to the constructor function itself. Like for example:

function Cat({ name, weight }) {
  return {
    name,
    weight,
    talk() {
      return `Hey! My name is ${this.name}. I weigh ${this.weight}`
    }
  }
}

Cat.whatAreCats = () => {
  return "Cats are awesome mammals that like booping things"
}

const cat = new Cat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
console.log(Cat.whatAreCats())
cat.talk()
Try in REPL

Prototypes: You Don't Actually Need Them

Prototypes in JavaScript are confusing because they aren't necessary, they are just helpful. For example, let's say we have both dogs and cats and we want them to both be able to talk. Maybe we have a movie full of talking animals voiced by Kevin Spacey and Josh Gad. Why? Because we can, thats why.

We can easily accomplish classical (hehe I made a pun) inheritance like this:

const AnimalMethods = {
  talk() {
    return `Hey! My name is ${this.name}. I weigh ${this.weight}`
  }
}

function Cat({ name, weight }) {
  return {
    name,
    weight,
    ...AnimalMethods,
  }
}

function Dog({ name, weight, barkLevel = 9000 }) {
  return {
    name,
    weight,
    barkLevel,
    ...AnimalMethods,
  }
}
const cat = new Cat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
const dog = new Dog({ name: 'Shadow', weight: 'a lot' })
console.log(cat.talk())
dog.talk()
Try in REPL

Imagine we wanted to modify the Dog or Cat constructor from outside their constructor function. How might we do that? This is where prototypes come in. Say for example, if instead of an Animal object we had an Animal constructor like this:

function Animal()
  return {
    talk() {
      return `Hey! My name is ${this.name}. I weigh ${this.weight}`
  }
}

And instead of returning new objects from our constructors we used the this object:

function Cat({ name, weight }) {
  this.name = name
  this.weight = weight
}

function Dog({ name, weight, barkLevel = 9000 }) {
  this.name = name
  this.weight = weight
  this.barkLevel = barkLevel
}

And we want to add the talk method to Cat and Dog objects. How would we do that? We could do it like this:

function Animal() {  
  return {
    talk() {
      return `Hey! My name is ${this.name}. I weigh ${this.weight}`
    }
  }
}

function Cat({ name, weight }) {
  this.name = name
  this.weight = weight
}

function Dog({ name, weight, barkLevel = 9000 }) {
  this.name = name
  this.weight = weight
  this.barkLevel = barkLevel
}

Cat.prototype = new Animal()
Dog.prototype = new Animal()

const cat = new Cat({ name: 'Mr. Ferguson, Sweatshirt', weight: 'a lot' })
const dog = new Dog({ name: 'Shadow', weight: 'a lot' })
console.log(cat.talk())
dog.talk()
Try in REPL

Prototypes let us modify a constructor's this object. That is all they really do in a nutshell. They modify the stuff returned from constructors. You don't even need them, they are just convenient.

tl/dr

Read the thing you lazy human. Just kidding, here is your summary;

  1. Everything in JS is an object. You can add properties to functions. Methods are just functions on an object.

  2. Everything is an object except for primitives which become objects when you use methods on them.

  3. new simply

    call

    's a function with

    this

  4. Primitives are immutable and objects are mutable

  5. Primitives are compared by value and objects are compared by reference

  6. Prototypes just automagically add methods to an object.

  7. Classes are functions

  8. Prototypes let us modify constructors