Static properties and methods
Most methods you write belong to individual objects. Call member.greet() and this points at that one member. But sometimes a piece of behavior belongs to the class itself, not to any single instance made from it. For those cases, JavaScript gives you static methods and properties.
You mark them with the static keyword:
class Member {
static staticMethod() {
alert(this === Member);
}
}
Member.staticMethod(); // true
Notice the call: Member.staticMethod(), not someMember.staticMethod(). The method hangs off the class name directly.
It’s just a property on the class
The static keyword is convenient syntax, nothing more. Writing the method inside the class body with static produces the same result as bolting it onto the class object afterward:
class Member { }
Member.staticMethod = function() {
alert(this === Member);
};
Member.staticMethod(); // true
Both approaches put staticMethod on the Member function object itself. A class in JavaScript is a function, and functions are objects, so you can assign properties to them.
Why does this === Member come out true? Because of the plain old “object before the dot” rule for this. In Member.staticMethod(), the object to the left of the dot is Member, so inside the call this is Member.
What statics are actually for
A static method models an operation that concerns the class as a whole, or several instances at once, rather than the internal state of one object.
Take a class of Recipe objects. You want a way to compare two recipes by date. That comparison isn’t really the business of any single recipe; it’s a relationship between recipes. So it fits naturally as a static method:
class Recipe {
constructor(title, date) {
this.title = title;
this.date = date;
}
static compare(recipeA, recipeB) {
return recipeA.date - recipeB.date;
}
}
// usage
let recipes = [
new Recipe("Pancakes", new Date(2021, 5, 12)),
new Recipe("Omelette", new Date(2021, 2, 3)),
new Recipe("Risotto", new Date(2021, 8, 20))
];
recipes.sort(Recipe.compare);
alert( recipes[0].title ); // Omelette
Recipe.compare sits “above” the recipes. It takes two of them and reports their ordering. Passing it straight to recipes.sort() works because sort expects exactly that shape of function: two arguments, a numeric result.
Factory methods
Another common use is the factory method: a static method that builds and returns an instance, often with some preset or computed data.
Suppose recipes can be created in more than one way:
- From explicit parameters (
title,date, and so on) — that’s the constructor’s job. - As a blank draft stamped with today’s date.
- Some other convenience shape down the line.
The second case reads well as a static method, Recipe.createDraft():
class Recipe {
constructor(title, date) {
this.title = title;
this.date = date;
}
static createDraft() {
// remember, here this === Recipe
return new this("Untitled draft", new Date());
}
}
let recipe = Recipe.createDraft();
alert( recipe.title ); // Untitled draft
The interesting line is new this(...). Since this inside the static method is the class Recipe, new this(...) means new Recipe(...). Writing new this instead of hard-coding new Recipe pays off with inheritance: a subclass that calls the inherited factory gets an instance of itself, not of the parent. More on that below.
Static methods also show up frequently in classes that talk to a database or an external store, where the operation targets records by identifier rather than an in-memory object:
// assuming Recipe is a class for managing stored recipes
// static method to remove a recipe by id:
Recipe.remove({id: 12345});
Static properties
Data can be static too. A static property looks like an ordinary class field, just with static in front:
class Recipe {
static publisher = "Arjun Rao";
}
alert( Recipe.publisher ); // Arjun Rao
Read it off the class: Recipe.publisher. As with methods, the declaration is equivalent to assigning the property to the class object directly:
Recipe.publisher = "Arjun Rao";
Static properties are handy for class-level data that every instance shares or that describes the class itself: a default configuration value, a running count of created instances, a registry, a version string.
Inheritance of statics
Statics are inherited, just like regular methods. A subclass can reach the parent’s static members through its own name.
Here Bird defines a static property habitat and a static method compare. Sparrow extends Bird and gets both, accessible as Sparrow.habitat and Sparrow.compare:
class Bird {
static habitat = "Sky";
constructor(name, speed) {
this.speed = speed;
this.name = name;
}
fly(speed = 0) {
this.speed += speed;
alert(`${this.name} flies at speed ${this.speed}.`);
}
static compare(birdA, birdB) {
return birdA.speed - birdB.speed;
}
}
// Inherit from Bird
class Sparrow extends Bird {
chirp() {
alert(`${this.name} chirps!`);
}
}
let sparrows = [
new Sparrow("Speckled Sparrow", 10),
new Sparrow("Brown Sparrow", 5)
];
sparrows.sort(Sparrow.compare);
sparrows[0].fly(); // Brown Sparrow flies at speed 5.
alert(Sparrow.habitat); // Sky
Sparrow never declares compare or habitat, yet Sparrow.compare and Sparrow.habitat resolve. When you call Sparrow.compare, the engine can’t find it on Sparrow, so it follows the chain up to Bird.compare and runs that.
How the lookup works: two prototype chains
extends wires up two separate [[Prototype]] links, and that’s the key to statics being inherited.
Spelled out, class Sparrow extends Bird sets up:
- The
Sparrowfunction prototypally inherits from theBirdfunction — this handles static members. Sparrow.prototypeprototypally inherits fromBird.prototype— this handles regular instance methods.
Because both links exist, inheritance covers static and instance sides at the same time. You can confirm each link directly:
class Bird {}
class Sparrow extends Bird {}
// the class-object chain — for statics
alert(Sparrow.__proto__ === Bird); // true
// the prototype chain — for instance methods
alert(Sparrow.prototype.__proto__ === Bird.prototype); // true
Summary
Static methods hold behavior that belongs to a class “as a whole” rather than to any one instance. Typical cases are a comparison like Recipe.compare(a, b) and a factory like Recipe.createDraft(). You mark them with static in the class body.
Static properties store class-level data that isn’t tied to an instance — defaults, counters, registries, and the like.
The syntax:
class MyClass {
static property = ...;
static method() {
...
}
}
Under the hood, a static declaration is the same as assigning to the class object itself:
MyClass.property = ...
MyClass.method = ...
Statics are inherited. For class B extends A, the prototype of B points to A (B.[[Prototype]] === A), so a static member not found on B is looked up on A. That second prototype link — between the class objects, alongside the usual one between the prototypes — is what makes static inheritance work.