We can also assign a method to the class as a whole. Such methods are called static.
In a class declaration, they are prepended by static
keyword, like this:
class User { static staticMethod() { alert(this === User); } } User.staticMethod(); // true
That actually does the same as assigning it as a property directly:
class User { } User.staticMethod = function() { alert(this === User); }; User.staticMethod(); // true
The value of this
in User.staticMethod()
call is the class constructor User
itself (the “object before dot” rule).
Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it.
For instance, we have Article
objects and need a function to compare them.
A natural solution would be to add Article.compare
static method:
class Article { constructor(title, date) { this.title = title; this.date = date; } static compare(articleA, articleB) { return articleA.date - articleB.date; } } // usage let articles = [ new Article("HTML", new Date(2019, 1, 1)), new Article("CSS", new Date(2019, 0, 1)), new Article("JavaScript", new Date(2019, 11, 1)) ]; articles.sort(Article.compare); alert( articles[0].title ); // CSS
Here Article.compare
method stands “above” articles, as a means to compare them. It’s not a method of an article, but rather of the whole class.
Another example would be a so-called “factory” method.
Let’s say, we need multiple ways to create an article:
Create by given parameters (title
, date
etc).
Create an empty article with today’s date.
…or else somehow.
The first way can be implemented by the constructor. And for the second one we can make a static method of the class.
Such as Article.createTodays()
here:
class Article { constructor(title, date) { this.title = title; this.date = date; } static createTodays() { // remember, this = Article return new this("Today's digest", new Date()); } } let article = Article.createTodays(); alert( article.title ); // Today's digest
Now every time we need to create a today’s digest, we can call Article.createTodays()
. Once again, that’s not a method of an article, but a method of the whole class.
Static methods are also used in database-related classes to search/save/remove entries from the database, like this:
// assuming Article is a special class for managing articles // static method to remove the article by id: Article.remove({id: 12345});
Static methods aren’t available for individual objects
Static methods are callable on classes, not on individual objects.
E.g. such code won’t work:
// ... article.createTodays(); /// Error: article.createTodays is not a function
A recent addition
This is a recent addition to the language. Examples work in the recent Chrome.
Static properties are also possible, they look like regular class properties, but prepended by static
:
class Article { static publisher = "Ilya Kantor"; } alert( Article.publisher ); // Ilya Kantor
That is the same as a direct assignment to Article
:
Article.publisher = "Ilya Kantor";
Static properties and methods are inherited.
For instance, Animal.compare
and Animal.planet
in the code below are inherited and accessible as Rabbit.compare
and Rabbit.planet
:
class Animal { static planet = "Earth"; constructor(name, speed) { this.speed = speed; this.name = name; } run(speed = 0) { this.speed += speed; alert(`${this.name} runs with speed ${this.speed}.`); } static compare(animalA, animalB) { return animalA.speed - animalB.speed; } } // Inherit from Animal class Rabbit extends Animal { hide() { alert(`${this.name} hides!`); } } let rabbits = [ new Rabbit("White Rabbit", 10), new Rabbit("Black Rabbit", 5) ]; rabbits.sort(Rabbit.compare); rabbits[0].run(); // Black Rabbit runs with speed 5. alert(Rabbit.planet); // Earth
Now when we call Rabbit.compare
, the inherited Animal.compare
will be called.
How does it work? Again, using prototypes. As you might have already guessed, extends
gives Rabbit
the [[Prototype]]
reference to Animal
.
So, Rabbit extends Animal
creates two [[Prototype]]
references:
Rabbit
function prototypally inherits from Animal
function.
Rabbit.prototype
prototypally inherits from Animal.prototype
.
As a result, inheritance works both for regular and static methods.
Here, let’s check that by code:
class Animal {} class Rabbit extends Animal {} // for statics alert(Rabbit.__proto__ === Animal); // true // for regular methods alert(Rabbit.prototype.__proto__ === Animal.prototype); // true
Static methods are used for the functionality that belongs to the class “as a whole”. It doesn’t relate to a concrete class instance.
For example, a method for comparison Article.compare(article1, article2)
or a factory method Article.createTodays()
.
They are labeled by the word static
in class declaration.
Static properties are used when we’d like to store class-level data, also not bound to an instance.
The syntax is:
class MyClass { static property = ...; static method() { ... } }
Technically, static declaration is the same as assigning to the class itself:
MyClass.property = ... MyClass.method = ...
Static properties and methods are inherited.
For class B extends A
the prototype of the class B
itself points to A
: B.[[Prototype]] = A
. So if a field is not found in B
, the search continues in A
.
importance: 3
As we know, all objects normally inherit from Object.prototype
and get access to “generic” object methods like hasOwnProperty
etc.
For instance:
class Rabbit { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); // hasOwnProperty method is from Object.prototype alert( rabbit.hasOwnProperty('name') ); // true
But if we spell it out explicitly like "class Rabbit extends Object"
, then the result would be different from a simple "class Rabbit"
?
What’s the difference?
Here’s an example of such code (it doesn’t work – why? fix it?):
class Rabbit extends Object { constructor(name) { this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // Error
First, let’s see why the latter code doesn’t work.
The reason becomes obvious if we try to run it. An inheriting class constructor must call super()
. Otherwise "this"
won’t be “defined”.
So here’s the fix:
class Rabbit extends Object { constructor(name) { super(); // need to call the parent constructor when inheriting this.name = name; } } let rabbit = new Rabbit("Rab"); alert( rabbit.hasOwnProperty('name') ); // true
But that’s not all yet.
Even after the fix, there’s still an important difference between "class Rabbit extends Object"
and class Rabbit
.
As we know, the “extends” syntax sets up two prototypes:
Between "prototype"
of the constructor functions (for methods).
Between the constructor functions themselves (for static methods).
In the case of class Rabbit extends Object
it means:
class Rabbit extends Object {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true
So Rabbit
now provides access to the static methods of Object
via Rabbit
, like this:
class Rabbit extends Object {} // normally we call Object.getOwnPropertyNames alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // a,b
But if we don’t have extends Object
, then Rabbit.__proto__
is not set to Object
.
Here’s the demo:
class Rabbit {} alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) false (!) alert( Rabbit.__proto__ === Function.prototype ); // as any function by default // error, no such function in Rabbit alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error
So Rabbit
doesn’t provide access to static methods of Object
in that case.
By the way, Function.prototype
also has “generic” function methods, like call
, bind
etc. They are ultimately available in both cases, because for the built-in Object
constructor, Object.__proto__ === Function.prototype
.
Here’s the picture:
So, to put it short, there are two differences:
class Rabbit | class Rabbit extends Object |
---|---|
– | needs to call super() in constructor |
Rabbit.__proto__ === Function.prototype | Rabbit.__proto__ === Object |