JavaScript Classes: Orientation

What’s a class?

A class is a sort of template for creating objects.

What are objects?

Let’s say objects are little bundles of data and behavior. We’re talking about JavaScript objects here. Without classes, a JS object might look something like this:

let user = {
  firstName: 'Jane',
  lastName: 'Smith',
  getFullName: function getFullName() {
    return this.firstName + ' ' + this.lastName;
  }
};

We call this “object literal notation”. The object literal is the thing with the curly brackets. It creates an object with a bunch of properties, defined by name-value pairs.

In this case, the names firstName, lastName, and getFullName represent public properties of the object. The values associated with those names are the values stored in those properties.

I can call user.getFullName() and it returns "Jane Smith".

Okay. I get objects. And classes are templates for creating objects. Why do we want templates for creating objects? Why not just use object literals?

The same sorts of reasons you’d want a template for creating anything: creating an abstraction for a complex task means you don’t have to think about the details involved, and you isolate those details so they only exist in one place in your code.

More concretely: What if you wanted three user objects to represent three different people? With object literal notation, that might look like this:

let user1 = {
    firstName: 'Jaime',
    lastName: 'Lannister'
    getFullName: function getFullName() {
      return this.firstName + ' ' + this.lastName;
    }
  },
  user2 = {
    firstName: 'Cersei',
    lastName: 'Lannister'
    getFullName: function getFullName() {
      return this.firstName + ' ' + this.lastName;
    }
  },
  user3 = {
    firstName: 'Tyrion',
    lastName: 'Lannister'
    getFullName: function getFullName() {
      return this.firstName + ' ' + this.lastName;
    }
  };

Yuck. There’s a lot of duplication here. If I want to change what getFullName does for a user object, I have to change it in three places. If I want to add a property to user objects, I have to add it in three places. Yuck.

To remove this duplication, you might consider using a function as a template for creating your user objects:

function createUser(firstName, lastName) {
  return {
    firstName: firstName,
    lastName: lastName,
    getFullName: function getFullName() {
      return this.firstName + ' ' + this.lastName;
    }
  };
}

let user1 = createUser('Jaime', 'Lannister'),
  user2 = createUser('Cersei', 'Lannister'),
  user3 = createUser('Tyrion', 'Lannister');

This puts the information about how a user object is created into one place. You simply call that function when you want to make a user, rather than outlining the entire user object.

Makes sense. But if I can use functions as templates for creating objects, why should I care about classes?

That’s a good question. The answer, unfortunately, is: “It’s complicated.”

Classes are very familiar for a lot of developers. For the communities around many popular programming languages, classes are the most common way to organize code. By far.

As JavaScript started taking over the world, those other developers started using it. Some a little, some a lot. And some of them missed classes. They missed classes so much that they came up with ways to create class-like things anyway. Adding an actual class keyword to the language is nice for those people.

Classes can be a mechanism for code reuse, too, via inheritance.

What’s inheritance?

Remember when we said a class was a template for creating objects? Inheritance is a way to make a new template by using an existing template as your starting point.

It’s another way of reducing duplication and creating more elegant abstractions.

Sounds pretty awesome.

It can definitely be handy. But it can also create some pretty awful messes. Like any tool or technique, before you go too deep with it, you should invest some time in really understanding it. Make sure you have a handle on the potential consequences of your design decisions.

In my opinion, inheritance tends to be overrated. It’s fine for some things, but it’s not a tool I reach for often. Composition is often preferable.

What the hell are you talking about?

Let’s talk more about inheritance later. It’s safe to pretend it doesn’t exist for now.

Fine. Let’s get to the point here. What does a JavaScript class look like?

Oh yeah. Like this:

class User {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFirstName() {
    return this.firstName + ' ' + this.lastName;
  }
}

let user1 = new User('Jaime', 'Lannister'),
  user2 = new User('Cersei', 'Lannister'),
  user3 = new User('Tyrion', 'Lannister');

This is similar to what we did above with the object literal notation and the function.

What’s different?

It’s mostly syntax, as you can see at a glance. We don’t define all the public properties as name-value pairs anymore. We define properties for data in the constructor. We define properties for behavior in function definitions. (BTW, in OO world, these are called “methods”, not “functions”.)

The constructor is a special function that gets called when you create an object using your class, via new User(...). Remember before when we said classes can be good for organizing code? Constructors are one aspect of that. They’re the appropriate place for code that helps construct the object.

And then getFirstName moves to a nice, declarative function (method) definition. It also only gets created once, regardless of how many users I create.

Wait. What?

Hmm. We’ll talk about this more when we get to inheritance in a future post. Let’s keep it at a high level for now.

Think of it this way: The function lives on the template, not on the instances. On the class, not the objects. When objects want to run it, they go back to the class and say something like “run this function, use me as this, and return the result.”

Um. Why?

Again, more on this in a future post. But for now: Imagine I create a hundred users. I’ll use way less memory if they all point to a single instance of getFirstName than if they each have their own copy of it.