222

What's the standard way to call static methods? I can think of using constructor or using the name of the class itself, I don't like the latter since it doesn't feel necessary. Is the former the recommended way, or is there something else?

Here's a (contrived) example:

class SomeObject {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(n);
  }

  printN(){
    this.constructor.print(this.n);
  }
}
Dan Dascalescu
  • 143,271
  • 52
  • 317
  • 404
simonzack
  • 19,729
  • 13
  • 73
  • 118
  • 9
    `SomeObject.print` feels natural. But `this.n` inside makes no sense since there is no instance, if we are talking about static methods. – dfsq Feb 20 '15 at 11:32
  • 4
    @dfsq `printN` is not static though. – simonzack Feb 20 '15 at 11:38
  • You are right, confused names. – dfsq Feb 20 '15 at 11:40
  • 2
    I am curious why this question does not have so many upvotes! Isn't this a common practice for creating utility functions? – Thoran Dec 01 '15 at 14:43
  • @Thoran no, not really common practice. An anti-pattern, more like. Typically static and non-static methods are *radically* different. Unifying them can be done (as demonstrated) but actually serves no useful OOP purpose. Instance methods work on an instance using the data *from* that instance. Just forwarding the call to a static method does none of that. Moreover, static methods are often a signal of where OOP itself breaks down as it's the only way to model non-instance blocks of functionality. For which JS already has functions anyway. – VLAZ Apr 12 '23 at 20:40

3 Answers3

253

Both ways are viable, but they do different things when it comes to inheritance with an overridden static method. Choose the one whose behavior you expect:

class Super {
  static whoami() {
    return "Super";
  }
  lognameA() {
    console.log(Super.whoami());
  }
  lognameB() {
    console.log(this.constructor.whoami());
  }
}
class Sub extends Super {
  static whoami() {
    return "Sub";
  }
}
new Sub().lognameA(); // Super
new Sub().lognameB(); // Sub

Referring to the static property via the class will be actually static and constantly give the same value. Using this.constructor instead will use dynamic dispatch and refer to the class of the current instance, where the static property might have the inherited value but could also be overridden.

This matches the behavior of Python, where you can choose to refer to static properties either via the class name or the instance self.

If you expect static properties not to be overridden (and always refer to the one of the current class), like in Java, use the explicit reference.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • can you explain the constructor property vs class method definition? – Chris Apr 26 '16 at 19:32
  • 2
    @Chris: Every class *is* a constructor function (just like you know it from ES5 without `class` syntax), there is no difference in the definition of the method. It's just a matter of how you look it up, via [the inherited `constructor` property](http://stackoverflow.com/q/4012998/1048572) or directly by its name. – Bergi Apr 26 '16 at 20:09
  • Another example is [PHP's Late Static Bindings](http://php.net/manual/en/language.oop5.late-static-bindings.php). Not only does this.constructor respect inheritance, but also helps you avoid having to update code if you change the class name. – ricanontherun Jul 19 '18 at 14:28
  • 1
    @ricanontherun Having to update the code when you change variable names is not an argument against using names. Also refactoring tools can do it automatically anyway. – Bergi Jul 19 '18 at 16:29
  • 1
    How to implement this in typescript ? It gives the error `Property 'staticProperty' does not exist on type 'Function'` – ayZagen Mar 28 '19 at 18:12
  • @ayZagen I dunno, I guess you should [ask a new question](https://stackoverflow.com/questions/ask) about how to do the TypeScript typing for this, and post the non-working code there. – Bergi Mar 28 '19 at 18:26
85

I stumbled over this thread searching for answer to similar case. Basically all answers are found, but it's still hard to extract the essentials from them.

Kinds of Access

Assume a class Foo probably derived from some other class(es) with probably more classes derived from it.

Then accessing:

  • from static method/getter of Foo class:
    • some probably overridden static method/getter:
      • this.method()
      • this.property
    • some probably overridden instance method/getter:
      • impossible by design
    • own non-overridden static method/getter:
      • Foo.method()
      • Foo.property
    • own non-overridden instance method/getter:
      • impossible by design
  • from instance method/getter of Foo class:
    • some probably overridden static method/getter:
      • this.constructor.method()
      • this.constructor.property
    • some probably overridden instance method/getter:
      • this.method()
      • this.property
    • own non-overridden static method/getter:
      • Foo.method()
      • Foo.property
    • own non-overridden instance method/getter:
      • not possible by intention unless using some workaround:
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor(Foo.prototype,'<property>' ).get.call(this)

Keep in mind that using this isn't working this way when using arrow functions or invoking methods/getters explicitly bound to custom value.

Background

  • When in context of an instance's method/getter
    • this is referring to current instance.
    • super is basically referring to same instance, but somewhat addressing methods/getters written in context of some class current one is extending (by using the prototype of Foo's prototype).
    • definition of instance's class used on creating it is available per this.constructor.
  • When in context of a static method/getter there is no "current instance" by intention and so:
    • this is available to refer to the definition of current class directly.
    • super is not referring to some instance either, but to static methods/getters written in context of some class current one is extending.

Conclusion

Try this code:

class A {
  constructor( input ) {
    this.loose = this.constructor.getResult( input );
    this.tight = A.getResult( input );
    console.log( this.scaledProperty, Object.getOwnPropertyDescriptor( A.prototype, "scaledProperty" ).get.call( this ) );
  }

  get scaledProperty() {
    return parseInt( this.loose ) * 100;
  }
  
  static getResult( input ) {
    return input * this.scale;
  }
  
  static get scale() {
    return 2;
  }
}

class B extends A {
  constructor( input ) {
    super( input );
    this.tight = B.getResult( input ) + " (of B)";
  }
  
  get scaledProperty() {
    return parseInt( this.loose ) * 10000;
  }

  static get scale() {
    return 4;
  }
}

class C extends B {
  constructor( input ) {
    super( input );
  }
  
  static get scale() {
    return 5;
  }
}

class D extends C {
  constructor( input ) {
    super( input );
  }
  
  static getResult( input ) {
    return super.getResult( input ) + " (overridden)";
  }
  
  static get scale() {
    return 10;
  }
}


let instanceA = new A( 4 );
console.log( "A.loose", instanceA.loose );
console.log( "A.tight", instanceA.tight );

let instanceB = new B( 4 );
console.log( "B.loose", instanceB.loose );
console.log( "B.tight", instanceB.tight );

let instanceC = new C( 4 );
console.log( "C.loose", instanceC.loose );
console.log( "C.tight", instanceC.tight );

let instanceD = new D( 4 );
console.log( "D.loose", instanceD.loose );
console.log( "D.tight", instanceD.tight );
Teocci
  • 7,189
  • 1
  • 50
  • 48
Thomas Urban
  • 4,649
  • 26
  • 32
  • 1
    `Own non-overridden instance method/getter / not possible by intention unless using some workaround` --- That's a real pity. In my opinion, this is a shortcoming of ES6+. Maybe it should be updated to allow simply referring to `method` -- i.e. `method.call(this)`. Better than `Foo.prototype.method`. Babel/etc. could implement using an NFE (named function expression). – Roy Tinker Oct 05 '17 at 18:13
  • `method.call( this )` is a probable solution except for the `method` isn't bound to the desired base "class" then and thus fails to be a _non-overriden instance method/getter_. It's always possible to work with class-independent methods that way. Nonetheless I don't think current design is that bad. In context of objects of class derived from your base class Foo there may be good reasons to override an instance method. That overriden method may have good reasons to invoke its `super` implementation or not. Either case is eligible and should be obeyed. Otherwise it would end in bad OOP design. – Thomas Urban Oct 17 '17 at 09:32
  • Despite the OOP sugar, ES methods are still _functions_, and people will want to use and reference them as such. My problem with ES class syntax is that it provides no direct reference to the currently executing method -- something that used to be easy via `arguments.callee` or an NFE. – Roy Tinker Oct 26 '17 at 19:58
  • Sounds like bad practice or at least bad software design anyway. I'd consider both points of view contrary to each other, as I don't see eligible reason in context of OOP paradigm that involves accessing currently invoked method by reference (which is not just its context as is available via `this`). It sound like trying to mix benefits of pointer arithmetics of bare C with higher-level C#. Just out of curiosity: what would you use `arguments.callee` for in cleanly designed OOP code? – Thomas Urban Nov 27 '17 at 11:48
  • I'm working in a large project built with Dojo's class system, which allows calling the superclass(es) implementation(s) of the current method via `this.inherited(currentFn, arguments);` -- where `currentFn` is a reference to the currently executing function. Not being able to reference the currently executing function directly is making it a little hairy in TypeScript, which takes its class syntax from ES6. – Roy Tinker Nov 27 '17 at 18:28
  • I see! This is mostly due to heterogenic setups demanding to mix the old world with the new one. I was using similar approach in a project myself and the reference on current method is required indeed for traversing the prototype chain to prevent infinite regressions when looking for next overloaded version. However this is past in a purely ES6 project. So, for now I stick with old code in old projects or try deriving from old-style classes and start using ES6 inheritance handling in such derived classes, only, though I don't know the specifics of Dojo classes, thus can't tell about your case. – Thomas Urban Nov 28 '17 at 19:00
22

If you are planning on doing any kind of inheritance, then I would recommend this.constructor. This simple example should illustrate why:

class ConstructorSuper {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(this.name, n);
  }

  callPrint(){
    this.constructor.print(this.n);
  }
}

class ConstructorSub extends ConstructorSuper {
  constructor(n){
    this.n = n;
  }
}

let test1 = new ConstructorSuper("Hello ConstructorSuper!");
console.log(test1.callPrint());

let test2 = new ConstructorSub("Hello ConstructorSub!");
console.log(test2.callPrint());
  • test1.callPrint() will log ConstructorSuper Hello ConstructorSuper! to the console
  • test2.callPrint() will log ConstructorSub Hello ConstructorSub! to the console

The named class will not deal with inheritance nicely unless you explicitly redefine every function that makes a reference to the named Class. Here is an example:

class NamedSuper {
  constructor(n){
    this.n = n;
  }

  static print(n){
    console.log(NamedSuper.name, n);
  }

  callPrint(){
    NamedSuper.print(this.n);
  }
}

class NamedSub extends NamedSuper {
  constructor(n){
    this.n = n;
  }
}

let test3 = new NamedSuper("Hello NamedSuper!");
console.log(test3.callPrint());

let test4 = new NamedSub("Hello NamedSub!");
console.log(test4.callPrint());
  • test3.callPrint() will log NamedSuper Hello NamedSuper! to the console
  • test4.callPrint() will log NamedSuper Hello NamedSub! to the console

See all the above running in Babel REPL.

You can see from this that test4 still thinks it's in the super class; in this example it might not seem like a huge deal, but if you are trying to reference member functions that have been overridden or new member variables, you'll find yourself in trouble.

Andrew Odri
  • 8,868
  • 5
  • 46
  • 55
  • 3
    But static function are no overridden member methods? Usually you are trying *not* to reference any overridden stuff statically. – Bergi Feb 21 '15 at 12:14
  • 2
    @Bergi I'm not sure I understand what you are pointing out, but one specific case I have come across would is with MVC model hydration patterns. Sub classes extending a model may want to implement a static hydrate function. However, when these are hard-coded in, base model instances are only ever returned. This is a pretty specific example, but many patterns that rely on having a static collection of registered instances would be effected by this. One big disclaimer is that we are trying to simulate classical inheritance here, rather than prototypal inheritance... And that is not popular :P – Andrew Odri Feb 24 '15 at 19:29
  • 1
    Yeah, as I concluded now in my own answer this is not even solved consistently in "classical" inheritance - sometimes you might want overrides, sometimes not. The first part of my comment pointed at static class functions, which I did not consider to be "members". Better ignore it :-) – Bergi Feb 24 '15 at 19:36