Classes

ES6 introduced the concept of "classes" in JavaScript, which differs from classes in other programming languages. Here, classes are special functions that serve as templates for creating objects that already contain data, properties associated with that data, and methods related to the manipulation of that data. These objects, properties, and methods are collectively called "members" of the class.

To define a class, use the class keyword. Following best practice and the convention established by JavaScript's built-in constructor functions, begin any identifier of a class with a capital letter:

class MyClass {}

Classes are intended to provide more accessible ways to work with advanced features of prototypes and constructor functions:

class MyClass {}

typeof MyClass;
> "function"

Because classes were partly added to make working with advanced JavaScript features easier and more appealing, they're sometimes referred to as "syntactic sugar". However, classes do more than just provide useful shorthand for working with prototypal inheritance. Introducing class syntax created opportunities to address longstanding design issues in JavaScript without introducing backwards compatibility issues. As one example, all code inside the body of a class is always evaluated in strict mode.

To create an instance of a class, use the new operator.

class MyClass {}

const myClassInstance = new MyClass();

myClassInstance;
> Object { }

Functions defined inside the body of a class are exposed as methods of each instance of that class.

class MyClass {
    classMethod() {
        console.log( "My class method." );
    }
}

const myClassInstance = new MyClass();

myClassInstance.classMethod();
> "My class method."

A method defined within a class becomes a method on the prototype of the resulting instance. Because of the nature of the prototype chain, you can call these methods directly on the resulting object:

class MyClass {
  classMethod() {
    console.log( "My class method." );
  }
}

const myClassInstance = new MyClass( "A string." );

myClassInstance;
> Object { }
    <prototype>: Object {  }
        classMethod: function classMethod()
        constructor: class MyClass { constructor(myPassedValue) }
        <prototype>: Object {  }

myClassInstance.classMethod();
> "My class method."

Creating an instance of a class calls a special constructor() method that performs any necessary "setup" for the newly-created instance and initializes any properties associated with it. Any arguments passed to the class when the instance is created are available to the constructor() method:

class MyClass {
  constructor( myPassedValue ) {
    console.log( myPassedValue );
  }
}

const myClassInstance = new MyClass( "A string." );
> "A string."

Within the body of a class, the value of this refers to the instance itself, with any properties defined on this exposed as properties of each instance of that class:

class MyClass {
  constructor( myPassedValue ) {
    this.instanceProperty = myPassedValue;
  }
}

const myClassInstance = new MyClass( "A string." );

myClassInstance;
> Object { instanceProperty: "A string." }

These properties are also available to all methods within the body of the class:

class MyClass {
  constructor( myPassedValue ) {
    this.instanceProp = myPassedValue;
  }
  myMethod() {
    console.log( this.instanceProp );
  }
}

const myClassInstance = new MyClass( "A string." );

myClassInstance.myMethod();
> "A string."

If you don't define a constructor() for your class, the JavaScript engine assumes an empty "default" constructor. Each class can only have one special method named constructor():

class MyClass {
  constructor() {}
  constructor() {}
}
> Uncaught SyntaxError: A class may only have one constructor

You can define a class using either a class declaration or a class expression. The previous examples have all been class declarations, which require names to be invoked using new. Class expressions can be named or left unnamed to create an "anonymous" class.

let ClassExpression = class {
    constructor() {}
};

ClassExpression;
> class  {}

One thing you can use anonymous class expressions for is functions that construct classes "on the fly:"

function classMaker() {
  return class {
    constructor() {}
  };
}

let MyVariable = classMaker();

MyVariable;
> class  {}

Redeclaring a class using a class declaration causes a syntax error:


class MyClass {
    constructor( ) {
        console.log( "My class." );
    }
};

class MyClass {
    constructor() {
        console.log( "My new class." );
    }
};
> Uncaught SyntaxError: redeclaration of class MyClass

However, class expressions let you redefine a class:

let ClassExpression = class MyClass { };

ClassExpression = class MyOtherClass {
    constructor( myString ) {
        this.myProp = myString;
    }
};

new ClassExpression( "String." );
> MyOtherClass {myProp: 'String.'}

You can't invoke a named class expression by name the way you can a class declaration. However, a class expression's assigned name is available as a property of the created instance, mostly to make debugging easier:

let MyVariable = class MyClass {};

MyClass;
> Uncaught ReferenceError: MyClass is not defined

MyVariable;
> class MyClass {}

MyVariable.name;
> "MyClass"

When you initialize a variable using a class expression, the hoisting rules of that variable are followed as expected. Class declarations follow the same "temporal dead zone" rules as let and const, and behave as if they haven't been hoisted to the top of their current scope, meaning that invoking a class before the class declaration causes an error:

{
    let myVar = new MyClass( "Property string." );

    class MyClass {
        myProp;

        constructor( myString ) {
            this.myProp = myString;
        }
    };
};
> Uncaught ReferenceError: Cannot access 'MyClass' before initialization

Check your understanding

Which of the following correctly defines a class?

myClass = class {}
class MyClass {}
new class()

How many constructor() methods can a class have?

None
One
Unlimited