Object-Oriented Programming

Methods

JavaScript, unlike most object-oriented programming languages, lets you work with objects without first having to define classes. You have already seen how to produce objects:

let harry = { name: 'Harry Smith', salary: 90000 }

According to the classic definition, an object has identity, state, and behavior. The object that you just saw certainly has identity—it is different from any other object. And the state is provided by the properties. Let's add behavior in the form of a “method”—that is, a function-valued property:

harry = {
  name: 'Harry Smith',
  salary: 90000,
  raiseSalary: function(percent) {
    this.salary *= 1 + percent / 100
  }
}

Now we can raise the employee's salary with the familiar dot notation:

harry.raiseSalary(10)

Note that raiseSalary is a function defined in the harry object. That function looks like an ordinary function, except for one twist: in the body, we refer to this.salary. When the function is called, this refers to the object to the left of the dot operator.

The this reference only works in functions declared with function, not with arrow functions. See XREF for more details.

There is a shortcut syntax for defining function-valued properties in object literals. Simply omit the colon and the function keyword:

harry = {
  name: 'Harry Smith',
  salary: 90000,
  raiseSalary(percent) {
    this.salary *= 1 + percent / 100
  }
}

This looks similar to a method definition in Java or C++, but it is just “syntactic sugar” for a function-valued property.

Prototypes

Suppose you have many employee objects similar to the one of the preceding section. Then you need to make a raiseSalary property for each of them. You can write a factory function to automate that task:

function createEmployee(name, salary) {
  return {
    name,
    salary,
    raiseSalary: function(percent) {
      this.salary *= 1 + percent / 100
    }
  }    
}

Still, each employee object has its own raiseSalary property, even though the property value is the same function for all employees. It would be better if all employees could share one function.

That is where prototypes come in. A prototype collects properties that are common to multiple objects. We can set up a prototype for employee methods:

const employeePrototype = {
    raiseSalary: function(percent) {
      this.salary *= 1 + percent / 100
  }
}

When creating an employee object, we set its prototype. The easiest way to do this is with the Object.create method. It creates a new object with a given prototype, to which we can then add properties:

function createEmployee(name, salary) {
  const result = Object.create(employeePrototype)
  result.name = name
  result.salary = salary
  return result
}

Constructors

In the preceding section, you saw how to write a factory function that creates new object instances with a shared prototype. There is special form for writing and invoking such factories, using the new operator. When a factory function is invoked with new, then:

  1. A new object is created whose prototype is factoryFunction.prototype
  2. this is bound to the newly created object
  3. The body of the function is invoked
  4. The new operator yields the created object

By convention, such factory functions are named after what would be the class in a class-based language. In our example, we call the factory function Employee, as follows:

function Employee(name, salary) {
  this.name = name
  this.salary = salary
}

Recall that any function is an object, so it can have properties. Each function has a prototype property, used when the function is called with new. You add methods to it like this:

Employee.prototype.raiseSalary = function(percent) {
  this.salary *= 1 + percent / 100
}

Now a client can call:

const harry = new Employee('Harry Smith', 100000)

The Employee function creates a new object. The this parameter points to that newly created object. The body of the Employee function serves as a constructor, setting the object properties, by using the this parameter. Finally, the prototype is automatically set to Employee.prototype, courtesy of the new operator.

The upshot of all this magic is that the new operator looks just like a constructor call in Java or C++. However, Employee isn't a class. It's just a function.

Then again, what is a class? A class is a set of objects with the same behavior. All objects that are obtained by calling new Employee(...) have the same behavior. In JavaScript, constructor functions are the equivalent of classes in class-based programming languages.

The Class Syntax

Nowadays, JavaScript has a class syntax that bundles up a constructor function and prototype methods in a familiar form. Here is the class syntax for the example of the preceding section:

class Employee {
  constructor(name, salary) {
    this.name = name
    this.salary = salary
  }
  raiseSalary(percent) {
    this.salary *= 1 + percent / 100
  }
}

This syntax does exactly the same as that of the preceding section. There still is no actual class. The constructor keyword defines the body of the Employee constructor function, so that you can construct an object by calling:

const harry = new Employee('Harry Smith', 100000)

The raiseSalary method is added to Employee.prototype.

By all means, use the class syntax. Just realize that it is nothing like a class declaration in a class-based language. You get a constructor function whose prototype has methods.

A class can have at most one constructor. After all, class is just syntactic sugar for defining a constructor function.

If you declare a class without a constructor, it automatically gets a constructor with an empty body.

Unlike in an object literal, in a class declaration, you do not use commas to separate the method definitions.
Classes, unlike functions are not hoisted. You need to declare a class before you can construct an instance.
The body of a class is executed in strict mode.

Instance Fields and Private Methods

You can dynamically set an object property in the constructor or any method by assigning to this.propertyName. These properties work the same way as instance fields in a class-based language.

class BankAccount {
  constructor() { this.balance = 0 }
  deposit(amount) { this.balance += amount }
  ...
}

An alternative notation is, as of early 2019, in “proposal stage 3”, which means that it is likely to be adopted in a future version of JavaScript.

You list the names and initial values of the fields in the class declaration, like this:

class BankAccount {
  balance = 0
  deposit(amount) { this.balance += amount }
  ...
}

A field is private (that is, inaccessible outside the methods of the class) when its name starts with #:

class BankAccount {
  #balance = 0
  deposit(amount) { this.#balance += amount }
  ...
}

A separate stage 3 proposal is to make methods private if their name starts with a #.

Static Methods and Fields

In a class declaration, you can define a method as static. Such a method does not operate on any object. It is a plain function that is a property of the class. Here is an example:

class BankAccount {
  ...
  static percentOf(amount, rate) { return amount * rate / 100 }
  ...
  addInterest(rate) {
    this.balance += BankAccount.percentOf(this.balance, rate)
  }
}

To call a static method, whether inside or outside the class, add the class name, as in the example above.

Of course, you can achieve the same effect by adding the function to the constructor:

BankAccount.percentOf = (amount, rate) => amount * rate / 100

Getters and Setters

A getter is a method with no parameters that is declared with the keyword get:

class Person {
  constructor(last, first) { this.last = last; this.first = first }
  get fullName() { return `${this.last}, ${this.first}` }
}

You call the getter without parentheses, as if you accessed a property:

const harry = new Person('Smith', 'Harry')
const harrysName = harry.fullName // 'Smith, Harry'

The harry object does not have a fullName property, but the getter method is invoked. You can think of a getter as a dynamically computed property.

You can also provide a setter, a method with one parameter:

class Person {
  ...
  set fullName(value) {
    const parts = value.split(/,\s*/)
    this.last = value[0]
    this.first = value[1]
 }
}

The setter is invoked when assigning to fullName:

harry.fullName = 'Smith, Harold'

When you provide getters and setters, users of your class have the illusion of using properties, but you control the property values and any attempts to modify them.

The this Reference

You have already seen how the this reference is set in constructors and methods:

That sounds simple enough, but there are two points of confusion.

Let us look at both of these points in more detail.

It is possible to define constructor functions so that they also do something useful when they are called without new. For example:

const price = Number("19.95") // Parses the string and returns a primitive number
const zeroObject = new Number(0)

In this case, the constructor is actually not useful—you don't want to construct number objects but use primitive numbers instead.

You could write constructor functions like that, taking different actions depending on the value of this. However, you risk confusing your fellow programmers.

It takes work to invoke a method without an object. The method first needs to be in a separate variable:

const action = BankAccount.prototype.deposit
action(1000) // Error—this doesn't point to any bank account

When the function is called, it fails when accessing this.balance.

Note that this does not work either:

const harrysAccount = new BankAccount()
const action = harrysAccount.deposit
action(1000) // Error—this doesn't point to any bank account

The expression harrysAccount.deposit is the exact same function as BankAccount.prototype.deposit

If you want an acrtion that deposits money in a specific account, just provide that:

const action = amount => harrysAccount.deposit(amount)

If you want an action that deposits money in an arbitrary account, you can do that too:

const action = (account, amount) =< account.deposit(amount)

You can also use the Function.bind method to yield a function that has this bound to a specific value. For example,

const action = BankAccount.prototoype.deposit.bind(harrysAccount)

Now we turn to the other source of this confusion: Using this in a callback function:

class BankAccount {
  ...
  spreadTheWealth(accounts) {
    accounts.forEach(function(account) {
      account.deposit(this.balance / accounts.length) // Error—this not correctly bound inside nested function
    })
  }
}

For historical reasons, this is set to undefined or the global object inside nested functions declared with the function keyword. The best remedy is to use an arrow function instead:

class BankAccount {
  ...
  spreadTheWealth(accounts) {
    accounts.forEach(account => {
      account.deposit(this.balance / accounts.length) // Error—this correctly bound
    })
  }
}

Another approach is to initialize another variable with this:

spreadTheWealth(accounts) {
   const that = this
   accounts.forEach(function(account) {
    account.deposit(that.balance / accounts.length)
  })
}

Subclasses

A key concept in object-oriented programming is inheritance. A class specifies behavior for its instances. You can form a subclass of a given class (called the superclass) whose instances behave differently in some respect, while inheriting other behavior.

A standard teaching example is an inheritance hierarchy with a superclass Employee and a subclass Manager. While employees are expected to complete their assigned tasks in return for receiving their salary, managers get bonuses on top of their base salary if they actually achieve what they are supposed to do.

In JavaScript, as in Java, you use the extends keyword to express this relationship among the Employee and Manager classes:

class Employee {
  constructor(name, salary) { ... }
  getSalary() { ... }
  raiseSalary(percent) { ... }
}

class Manager extends Employee {
  constructor(name, salary, bonus) {
    super(name, salary)
    this.bonus = bonus
  }
  getSalary() { return super.getSalary() + this.bonus }
}

When super is used in the constructor, it invokes the superclass constructor. In methods, super calls a superclass method. In this example, the getSalary method is overridden to add the bonus to the salary returned from the getSalary method of the superclass.

Behind the scenes, a prototype chain is established. The prototype of the Manager constructor is set to the Employee constructor. In that way, any method that is not defined in the subclass is looked up in the superclass.

Prior to the extends syntax, JavaScript programmers had to establish such a prototype chain themselves—see .

You can override getter and setter methods in a subclass. Suppose the superclass has a salary getter that yields a private field value:

class Employee {
  ...
  get salary() { return this.#salary } 
}

Then you can override the getter in the subclass:

class Manager extends Employee {
  ...
  get salary() { return super.salary + this.bonus }
}