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
errorwhen 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 function
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
statickeyword 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'
The difference here is that the method exists on the
Userclass itself, rather than on objects created withnew 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]
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]
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'
>
class User {static get defaultName() {return 'Amir';}constructor(name=User.defaultName) {this.name = name;}}[new User('Betty').name, new User().name];Result:
['Betty', 'Amir']
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:
undefined
>
class User {static get defaultName() {return 'Amir';}constructor(name=User.defaultName) {this.name = name;}}new User('Betty').defaultName;Result:
undefined
Inside of a static method or accessor,
thisrefers to the class itself.>
class User {static get myself() {return this;}}User.myself === User;Result:
true
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'
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']
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
Progressclass that tracks a user's progress through all of the courses. Normally, we instantiate it using the normal constructor,new Progress(...).The
Progressclass also has an alternate constructor,static empty(...). It builds an "empty"Progressinstance 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
emptyalternate constructor when someone visits a lesson but isn't logged in. Other code in the system needs aProgressinstance, even if there's no registered user present. We give that code aProgress.empty().Alternate constructors are great for situations like this. However, they should generally be short, like the one-line
emptystatic 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.