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

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

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

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

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

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

3 ответа

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

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, используйте явную ссылку.

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

Виды доступа

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

Затем доступ

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

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

Фон

  • Когда в контексте метода экземпляра или получателя
    • 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 );

Если вы планируете заниматься каким-либо наследством, я бы порекомендовал 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 все еще думает, что это в суперклассе; в этом примере это может показаться не таким уж большим делом, но если вы пытаетесь ссылаться на функции-члены, которые были переопределены, или на новые переменные-члены, вы окажетесь в беде.

Другие вопросы по тегам