(2019) JavaScript OOP

Objectives

  • Describe the difference between classes and objects

  • Use JavaScript syntax to define a class

  • Instantiate an object from a class

  • Explain the use of the constructor method

  • Utilize the this keyword in the scope of class variables and methods

  • Understand class variables and how they differ from instance variables

Objects and Classes

JavaScript is an object oriented programming language (or at least it does its best to approximate one). Object oriented programming is characterized by the creation and use of collections of data and behavior that act like everyday physical objects. Every day we interact with objects like chairs, beverages, cars, other people, etc. These objects have properties that define them and behaviors we can execute to interact with them. Over many years, programmers have found that designing systems to reflect discrete, everyday objects makes the systems easier to understand, write, test, and maintain.

You will hear people say that "everything is an object" in JavaScropt. What this means is that nearly every variable you declare already has a set of properties and functions that it can use because it is an object. Every string, number, array, object, etc has a set of behaviors and properties that are "baked-in" because they are instances of a class. For example, every string you declare is an instance of a built-in JavaScript String class and because of that you can use any of the built-in string functions like toUpperCase() and toLowerCase() on your own string.

Frequently, we want to design our own special data types that hold our own specific data and organize it in a certain way. JavaScript gives us a string and a number, but what if we want to store a bunch of data where each item contains a string and an number and a function that prints them out nicely? We can make a class for that and then each object we make from that class will have all that baked-in.

Codealong: Write a basic class

I like dogs. Here's what a Dog class definition might look like in JavaScript:

class Dog {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  barkHello() {
    console.log(`Woof! I am called ${this.name} and I am ${this.age} human-years old.`)
  }
}

The Dog class is a collection of variables and methods. The constructor method is a special method JavaScript executes when a new dog is created. We will talk about this shortly. The variables in this class are this.name and this.age. The method in this class is barkHello.

Instantiating objects from classes

By defining the dog class we now know the structure that each of our dogs will have. It's like we have made a new variable type! But how do we make new dogs, or new variables of this type? We call our class name as a function with the new keyword:

const gracie = new Dog('Gracie', 8);

This will create a new object according to our Dog class specification. When that happens, JavaScript runs our constructor method. Our constructor method sets the name of this dog to 'Gracie' and sets her age to 8 years old.

So, what's this this keyword? When we make objects from this class, the this keyword inside each object refers to that specific object. This allows each object made from a class to maintain its own copies of variables.

this can be a strange programming paradigm. Imagine that you are an object. You represent the this. If you wanted to access your arm, it would be this.arm. Your name would probably be stored in this.name. This lets each object made from a class keep reference to its own data and function members. Not every 'Person' has the same name so we want individual People to maintain their own names. The this keyword is each objects' identity.

Let's make a couple more dogs so we can see the differences in our dog objects:

const spitz = new Dog('Spitz', 5);  // lead dog
const buck = new Dog('Buck', 3);    // upstart newcomer

Each dog has its own name and age. Let's have them say hello:

gracie.barkHello();  // Woof! I am called Gracie and I am 8 human-years old.'
spitz.barkHello();   // Woof! I am called Spitz and I am 5 human-years old.'
buck.barkHello();    // Woof! I am called Buck and I am 3 human-years old.'

Let's add some more code to better see what is going on behind the scenes.

Class (static) vs Instance members

In our Dog class, we have variables attached to the this property that exist independently for each object that's created. These are called instance variables. Each object instance has its own copies of these variables and they can vary across objects. We can also attach variables to the class itself so that there's one single thing that exists for an entire class. These go by different names in different languages but in JavaScript are called static variables.

Suppose we want to keep a tally of how many dogs we have running around in our app. We could put a copy of the tally in each dog object but that's not efficient: we would be duplicating a value in memory multiple times and we would have to update the value in every dog object in order to keep it accurate. It's much better if we can store it once in the class. That way, each dog object can access it but we only need to store it and set it in one place.

There is a nice, clean way to do this but we must use a very new feature called "class fields syntax" which simplifies the adding of member variables to a class as well as giving us the ability to easily add static variables. This is an experimental feature and is not supported in all browsers as of 2019 but when we develop using React (or any build chain that involves the Babel transpiler) we can have our newer-syntax JavaScript "transpiled" into older JavaScript that does run in older browsers.

Let's update our class to add a totalDogs variable to the class. Let's also add a line that increments this value inside our constructor method. And just for fun, let's add another line to our barkHello method that references this total:

class Dog {
  static totalDogs = 0;

  constructor(name, age) {
    this.name = name;
    this.age = age;
    Dog.totalDogs++;
  }

  barkHello() {
    console.log(`Woof! I am called ${this.name} and I am ${this.age} human-years old.`);
    console.log(`There are ${Dog.totalDogs} dogs in this room!`);
  }
}

Now when we create a new dog, the constructor method increments the totalDogs counter which is stored in the Dog class itself. We can access the value stored in Dog.totalDogs inside our script and each dog object can access it from their own functions.

Inheritance

Another amazing feature of classes and one of the central aspects of Object Oriented Programming is the idea of having one class inherit functionality and basic structure from another class. Using this idea, you can organize your classes from general to specific where "child" classes that inherit functionality from "parent" classes can refine and extend that parent's behaviors.

For example, we have a Dog class and all dogs bark but not all dogs can fetch. We could add this functionality to the base Dog class but since not all dogs fetch this doesn't make good design sense. What we should do is use the Dog class as our parent or super class and then create a child or sub class that has this more specific behavior. Let's make a Retriever class:

class Retriever extends Dog {
  constructor(name, age) {
    super();
  }

  fetch() {
    console.log(`Woof! Here is the ball you just threw.`);
  }
}

The extends keyword is the special syntax here. We say that Retriever extends Dog and this tells JavaScript that any object made from the Retriever class will also have all the data and behavior from the Dog class. There is also a new function in our constructor. Calling super() invokes the constructor from the parent but does all of its operations for the new object we are instantiating. Let's try it out:

const boots = new Retriever('Boots', 5);  // good girl
const miriya = new Dog('Miriya', 10);     // Zentraedi airforce leader

boots.barkHello();  // Woof! I am called Boots and I am 5 human-years old.'
boots.fetch();      // Woof! Here is the ball you just threw.
miriya.fetch();     // ERROR, the Dog class doesn't have a fetch method

So we can see that the subclass dog, an instance of the Retriever class, has not only the new fetch() method but also the barkHello() method from the Dog class. But notice that any object instantiated from the Dog class does NOT have the fetch() method. That is because inheritance only works downward. Superclasses pass members down to subclasses, not the other way around.

Principles of Object Oriented Programming

There are three main aspects of OOP that people cite as the main advantages. Sometimes this question comes up in interviews so now that we have seen the machanics of OOP in JavaScript let's see how they make up the paradigm of OOP:

Encapsulation

That is a big, fancy word that means that your data and functions inside a class are protected and scoped to that class. Each object encapsulates its own data attributes and functions and because they are inside of an object, they are protected from global variable accesses. It also adds the benefit of organization because a class only needs to contain things relevant to the class itself. Classes are generally self-contained, modular and reusable - which are all good things in programming.

When we add a static variable to a class that can only be accessed through that class, that is a perfect example of the benefit of encapsulation. When we attach the data that comes in via the constructor to the individual objects we instantiate, that is also one of the virtues of encapsulation.

Inheritance

As we showed above, classes can incorporate behavior from other classes by inheriting from them using the extends keyword. This leads to a lot of code savings. If we need more specific or additional functionality in a class we can simply extend that class and not need to rewrite anything. Most large software solutions, including Node and the DOM built into the browser, are built as a big collection of general-to-specific classes.

Polymorphism

A term meaning "many shapes", polymorphism refers to the ability of a function or even a whole object to take on different behaviors depending on which class is being called. Though the name of a function can stay the same as it is inherited to other classes, those subclasses can override that function with new or extended behavior. The code calling that method calls it the same as it would any other time but it will do different things depending on what object it was called on, hence "many shapes".

Exercise: Create Your Own JavaScript Class

Now that we've seen a dog-based example of JavaScript classes, let's make one a bit more sophisticated. Create a BankAccount class.

  • Bank accounts should be created with the accountType property (like "savings" or "checking").

  • Bank accounts should have a class-level (static) variable tracking the total amount of money in all accounts.

  • Each account should keep track of it's current balance.

  • Each account should have access to a deposit and a withdraw method.

  • Each account should start with a balance set to zero.

Things to think about:

  • Any starting values for variables should be set in the constructor method

  • Static variables are declared using the static keyword inside the class but outside any methods

  • Instance variables are declared inside the constructor method

  • Does your constructor method need to accept any parameters?

Bonus: Start each account with an additional overdraftFees property that starts at zero. If a call to withdraw ends with the balance below zero then overdraftFees should be incremented by twenty.

Bonus bonus: Create a ChildBankAccount class that overrides the withdraw method to eliminate the overdraft penalty fee and simply disallow the withdrawal if there aren't enough funds.

Closing Thoughts

  • A Class is a pre-defined structure that contains attributes and methods that are grouped together logically.

  • An Object is an instance of a Class structure. A class could be thought of as a house blueprint and an object as a house built from the blueprint.

  • Classes are defined via a method call. Classes contain an constructor method that takes in parameters to be assigned to member variables (properties) of an Object.

  • Instance variables contain data types declared in the class but defined in each object.

  • Static variables and methods contain data and actions that live on the class itself.

  • The this keyword lets us distinguish between variables that exist in different objects and refer to the object we are currently in.

Last updated