Execute Program

Modern JavaScript: Static Methods

Welcome to the Static Methods lesson!

This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!

  • So far, we've only seen methods on instances. If we try to call an instance method on the class itself, we get an error. (You can type error when a code example will throw an error.)

  • >
    class Dog {
    constructor(name, likesChicken) {
    this.name = name;
    this.likesChicken = likesChicken;
    }

    wantsChicken() {
    return this.likesChicken;
    }
    }
    Dog.wantsChicken();
    Result:
    TypeError: Dog.wantsChicken is not a functionPass Icon
  • Although we can't call instance methods directly on a class, it is possible to define methods on the classes themselves. These are called "static methods". In the next example, note the static keyword before the method definition.

  • >
    class User {
    constructor(name) {
    this.name = name;
    }

    static defaultNames() {
    return ['Amir', 'Betty', 'Cindy'];
    }
    }
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    User.defaultNames()[2];
    Result:
    'Cindy'Pass Icon
  • The difference here is that the method exists on the User class itself, rather than on objects created with new User():

  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    User.defaultNames();
    Result:
  • Note: this code example reuses elements (variables, etc.) defined in earlier examples.
    >
    const user = new User();
    user.defaultNames();
    Result:
  • Sometimes, we'll want multiple ways to create instances of a class. For example, we might want to have a separate function for creating administrator users (as opposed to regular, non-admin users).

  • In some programming languages, we can declare multiple constructors taking different parameters. That's called "constructor overloading". JavaScript doesn't support constructor overloading, but we can use static methods to achieve the same effect with a little extra work.

  • >
    class User {
    constructor(name, isAdmin=false) {
    this.name = name;
    this.isAdmin = isAdmin;
    }

    static newAdmin(name) {
    return new User(name, true);
    }
    }

    [new User('Amir').isAdmin, User.newAdmin('Betty').isAdmin];
    Result:
    [false, true]Pass Icon
  • Here's a code problem:

    Use a static method to simulate an alternate constructor in this rectangle class. The static method's name should be square. It should take one argument, size, and return a rectangle whose width and height are both equal to that size.

    class Rectangle {
    constructor(width, height) {
    this.width = width;
    this.height = height;
    }

    static square(size) {
    return new Rectangle(size, size);
    }
    }

    const square = Rectangle.square(5);
    [square.width, square.height];
    Goal:
    [5, 5]
    Yours:
    [5, 5]Pass Icon
  • Accessors (getters and setters) can also be static. That means that the getter or setter is accessible on the class itself, not on instances.

  • >
    class User {
    static get defaultName() {
    return 'Amir';
    }
    }
    User.defaultName;
    Result:
    'Amir'Pass Icon
  • >
    class User {
    static get defaultName() {
    return 'Amir';
    }

    constructor(name=User.defaultName) {
    this.name = name;
    }
    }

    [new User('Betty').name, new User().name];
    Result:
    ['Betty', 'Amir']Pass Icon
  • If we try to access a static accessor on an instance, it won't be there. From JavaScript's perspective, we're trying to access a property that doesn't exist, so we get undefined, just like with any other object property that doesn't exist.

  • >
    const obj = {};
    obj.defaultName;
    Result:
    undefinedPass Icon
  • >
    class User {
    static get defaultName() {
    return 'Amir';
    }

    constructor(name=User.defaultName) {
    this.name = name;
    }
    }

    new User('Betty').defaultName;
    Result:
    undefinedPass Icon
  • Inside of a static method or accessor, this refers to the class itself.

  • >
    class User {
    static get myself() {
    return this;
    }
    }
    User.myself === User;
    Result:
    truePass Icon
  • JavaScript classes are themselves a type of object, so they can have properties just like other objects can.

  • >
    class User {
    }

    User.defaultName = 'Amir';

    User.defaultName;
    Result:
    'Amir'Pass Icon
  • When we set a property like this.realDefaultName = ..., we're setting the property on the class. In the next example, we use this to implement a static setter and getter pair.

  • >
    class User {
    static get defaultName() {
    return this.realDefaultName;
    }

    static set defaultName(newDefaultName) {
    /* We're inside of a static setter, so this line sets a property on
    * the class itself, not on an instance. */
    this.realDefaultName = newDefaultName;
    }

    constructor(name=User.realDefaultName) {
    this.name = name;
    }
    }

    User.defaultName = 'Amir';
    const amir = new User();

    User.defaultName = 'Betty';
    const betty = new User();

    [amir.name, betty.name];
    Result:
    ['Amir', 'Betty']Pass Icon
  • What do we use static methods for in real systems? One common example is alternate constructors, which we saw earlier in this lesson.

  • Execute Program's own source code uses static methods as alternate constructors in a few places. For example, we have a Progress class that tracks a user's progress through all of the courses. Normally, we instantiate it using the normal constructor, new Progress(...).

  • The Progress class also has an alternate constructor, static empty(...). It builds an "empty" Progress instance with no lessons finished.

  • >
    class Progress {
    ...

    static empty() {
    return new Progress([], [])
    }
    }
  • (We simplified that code a bit, and we removed its TypeScript type annotations, which we cover in our TypeScript course. However, the real function is only one line long, just like what's shown above.)

  • We use this empty alternate constructor when someone visits a lesson but isn't logged in. Other code in the system needs a Progress instance, even if there's no registered user present. We give that code a Progress.empty().

  • Alternate constructors are great for situations like this. However, they should generally be short, like the one-line empty static method above. If an alternate constructor does a lot of work, it may be more readable when converted into its own class, standalone function, or module.

  • Static methods are also used as utility methods. Static properties can store metadata, maintain caches, or hold configuration properties.

  • Static methods and properties are an important language feature, but they're only needed occasionally. Think of them as a way to handle unusual situations where an instance method just doesn't work.