Вызов статических методов из обычных методов класса ES6

Какой стандартный способ вызова статических методов? Я могу придумать использование constructor или используя имя самого класса, мне он не нравится, поскольку он не нужен. Является ли прежний рекомендованным способом, или есть что-то еще?

Вот пример (надуманный):

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

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

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

Ответ 1

Оба способа жизнеспособны, но они делают разные вещи, когда дело доходит до наследования с переопределенным статическим методом. Выберите тот, чье поведение вы ожидаете:

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

Обращение к статическому свойству через класс будет фактически статическим и постоянно дает то же значение. Использование this.constructor вместо этого будет использовать динамическую отправку и обратиться к классу текущего экземпляра, где статическое свойство может иметь унаследованное значение, но также может быть переопределено.

Это соответствует поведению Python, где вы можете ссылаться на статические свойства либо через имя класса, либо экземпляр self.

Если вы ожидаете, что статические свойства не будут переопределены (и всегда относятся к одному из текущего класса), как в Java, используйте явную ссылку.

Ответ 2

Я наткнулся на этот поток, ища ответ на аналогичный случай. В основном все ответы найдены, но все равно трудно извлечь из них необходимые вещи.

Виды доступа

Предположим, что класс Foo, вероятно, получен из некоторого другого класса (ов) с, вероятно, большим количеством классов, полученных из него.

Затем доступ к

  • из статического метода/getter Foo
    • некоторый, вероятно, переопределенный статический метод /getter:
      • this.method()
      • this.property
    • некоторый, вероятно, метод переопределенный экземпляр: метод getter:
      • невозможно по дизайну
    • собственный неперекрываемый статический метод /getter:
      • Foo.method()
      • Foo.property
    • собственный неперекрываемый экземпляр метод /getter:
      • невозможно по дизайну
  • из экземпляра метода/получателя Foo
    • некоторый, вероятно, переопределенный статический метод /getter:
      • this.constructor.method()
      • this.constructor.property
    • некоторый, вероятно, метод переопределенный экземпляр: метод getter:
      • this.method()
      • this.property
    • собственный неперекрываемый статический метод /getter:
      • Foo.method()
      • Foo.property
    • собственный неперекрываемый экземпляр метод /getter:
      • невозможно по намерению, если не использовать какое-либо обходное решение:
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Имейте в виду, что использование this не работает таким образом при использовании функций стрелок или при вызове методов/геттеров, явно связанных с пользовательским значением.

Фон

  • Когда в контексте метода экземпляра или getter
    • this ссылается на текущий экземпляр.
    • super в основном относится к тому же экземпляру, но несколько методов адресации и геттеры, написанные в контексте некоторого текущего класса, расширяются (используя прототип прототипа Foo).
    • определение класса экземпляра, используемого при его создании, доступно за this.constructor.
  • Когда в контексте статического метода или геттера нет "текущего экземпляра" по намерению и так
    • this можно напрямую ссылаться на определение текущего класса.
    • super также не относится к какому-либо экземпляру, но к статическим методам и геттерам, написанным в контексте некоторого текущего класса, расширяется.

Заключение

Попробуйте этот код:

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 );

Ответ 3

Если вы планируете делать какое-либо наследование, я бы рекомендовал this.constructor. Этот простой пример должен иллюстрировать, почему:

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() будет записывать ConstructorSuper Hello ConstructorSuper! в консоль
  • test2.callPrint() запишет ConstructorSub Hello ConstructorSub! в консоль

Именованный класс не будет иметь дело с наследованием, если вы явно не переопределите каждую функцию, которая ссылается на именованный класс. Вот пример:

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() будет записывать NamedSuper Hello NamedSuper! в консоль
  • test4.callPrint() будет записывать NamedSuper Hello NamedSub! в консоль

Посмотрите все вышеперечисленное в Babel REPL.

Вы можете видеть из этого, что test4 все еще думает об этом в суперклассе; в этом примере это может показаться не огромным делом, но если вы пытаетесь ссылаться на функции-члены, которые были переопределены или новые переменные-члены, вы столкнулись с проблемами.