Classes

Classes are objects that share the same properties.

A member of a class is called the instance of that class. For example, in the example below, object is an instance of the Object class.

const object = new Object();

Defining a class

JavaScript classes are implemented using prototype-based inheritance. If two objects are members from the same class, they would inherit from the same prototype.

Using a factory function

Not the idiomatic way to define a class.

const vectorPrototype = {
  getMagnitude() {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }
}

function createVector(x, y) {
  // Recall that `Object.create` creates
  // an object using the given prototype
  const vector = new Object(vectorPrototype);
  // Set states
  vector.x = x;
  vector.y = y;
  // Return the object 
  return vector;
}

// Creating an instance of `Vector`
// `Vector.prototype` is inherited by `vector`
const vector = createVector(3, 4);
console.log(vector); //=> {...}
console.log(vector.getMagnitude()); //=> 5

Using a constructor function

A constructor is a function designed to be invoked with new.

This was the way to declare classes before ES6 introduced the class syntax.

// Constructor function names are by
// convention written using PascalCase
function Vector(x, y) {
  this.x = x;
  this.y = y;
}

Vector.prototype = {
  getMagnitude() {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }
}

// Creating an instance of `Vector`
// Inherited properties are not logged here
const vector = new Vector(3, 4);
console.log(vector); //=> {...}
console.log(vector.getMagnitude()); //=> 5

Using the class syntax

The class syntax is introduced in ES6 as a "syntactic sugar" for defining classes. Unlike function declarations, they are "hoisted" to the top of the scope.

At the end of the day, your class definition will still be evaluated as a constructor function just like the above.

class Vector {
  // Make a constructor function here
  // You can omit `constructor` if your class
  // doesn't need to do any initialization
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  getMagnitude() {
    return Math.sqrt(this.x ** 2 + this.y ** 2);
  }
}

// `Vector` is a constructor function
console.log(Vector); //=> <Function>

// Creating an instance of `Vector`
// Inherited properties are not logged here
const vector = new Vector(3, 4);
console.log(vector); //=> {...}
console.log(vector.getMagnitude()); //=> 5

The prototype and the constructor property

prototype

Although almost all objects have a prototype, only functions (excluding arrow functions, generator functions, and async functions) have the prototype property. (For example, it makes sense to query <functionName>.prototype.)

The property will<functionName>.prototype be used as the object's prototype when you call new <functionName>().

constructor

The prototype property is an object that contains a single, non-enumerable constructor property that links back to the constructor function.

const MyConstructor = function () {};

const thePrototype = MyConstructor.prototype;
console.log(MyConstructor.prototype); //=> { constructor: ... }

const theConstructor = MyConstructor.prototype.constructor;
console.log(theConstructor === MyConstructor); //=> true

Since constructor is a property of an object's prototype, then naturally, all object will also inherit the constructor property.

const MyClass = function () {};

const myClass = new MyClass();
console.log(myClass.constructor); //=> <Function>
console.log(myClass.constructor === MyClass); //=> true

Modifying prototypes

JavaScript's prototype-based inheritance is dynamic: if you add a new method to it, then the classes inheriting from it will also inherit the new method.

Note, however, that this is considered a bad idea because "it will cause confusion and compatibility problems" with future versions of JavaScript (Flanagan).

Extending a class

If class B extends class A:

  • It means that class B will inherit properties of class A. (B can override some or all of the inherited properties, though.)
  • We can say that class B is the subclass of class A.
  • We can say that class A is the superclass of class B.

Extending using constructor functions

In the example below,

  • objects created with new Subclass() will inherit from Subclass.prototype,
  • but Subclass.prototype is an object that inherits from Superclass.prototype (recall Object.create)
  • so, objects created with new Subclass() will inherit from both Subclass.prototype and Superclass.prototype
function Superclass() {...}
Superclass.prototype = {...};

// `Subclass` inherits from `Superclass`
function Subclass() {...}
Subclass.prototype = Object.create(Superclass.prototype);

// If we want to override the constructor
Subclass.prototype.constructor = Subclass;
// If we want to override a method
Subclass.prototype.methodName = function () {...};

Extending using the class syntax

Use the extends keyword.

Inside the subclass constructor, you can use super() to call the superclass' constructor.

class Subclass extends Superclass {
  constructor(...args) {
    // ... do something ...
    super(...args);
    // ... do something ...
  }

  // If you want to implement or or override
  // a method, just write it here.
  methodName() {
    // ... do something ...
  }
}

Static functions

Using the class syntax, you can declare static functions: functions that are called on the class itself, rather than the class instances.

Just put static before the function name inside the class definition.

class Vector {
  // Make a constructor function here
  // You can omit `constructor` if your class
  // doesn't need to do any initialization
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  static add(v1, v2) {
    const rx = v1.x + v2.x;
    const ry = v1.y + v2.y;
    return new Vector(rx, ry);
  }
}

const vector1 = new Vector(3, 4);
const vector2 = new Vector(-2, -2);

// Call static functions using the class name
const vectorR = Vector.add(vector1, vector2);
console.log(vectorR); //=> { x: 1, y: 2 }

Instance and static fields

At the time of writing, there is no finalized specification for defining fields, although "standardization is underway" (Flanagan).

The current way:

  • To write instance fields do so in the constructor, using this.<fieldName>
  • To write static fields assign it to the class outside the class body, after the class have been defined
class MyClass {
  constructor() {
    // Write instance fields inside the constructor
    this.instanceField1 = 'someValue';
    this.instanceField2 = 'someValue';
  }
}

// Write static fields outside the class
MyClass.staticField1 = 'anotherValue';
MyClass.staticField2 = 'anotherValue';

References

  • JavaScript: The Definitive Guide, 7th Edition (David Flanagan)Chapter 9. Classes