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


176

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

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

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

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

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

8
SomeObject.printчувствует себя естественно. Но this.nвнутри нет смысла, так как нет примера, если мы говорим о статических методах.
dfsq

3
@dfsq printNне является статичным, хотя.
simonzack

Вы правы, запутанные имена.
dfsq

1
Мне любопытно, почему в этом вопросе не так много голосов! Разве это не распространенная практика для создания служебных функций?
Торан

Ответы:


211

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

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
@Chris: каждый класс является функцией конструктора (как вы знаете это из ES5 без classсинтаксиса), в определении метода нет различий. Это просто вопрос того, как вы смотрите это, через унаследованное constructorсвойство или напрямую по его имени.
Берги

Другой пример - поздние статические привязки PHP . This.constructor не только уважает наследование, но также помогает избежать необходимости обновлять код при изменении имени класса.
ricanontherun

@ricanontherun Необходимость обновления кода при изменении имен переменных не является аргументом против использования имен. Также инструменты рефакторинга могут делать это автоматически в любом случае.
Берги

Как реализовать это в машинописи? Property 'staticProperty' does not exist on type 'Function'
Выдает

72

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

Виды доступа

Предположим, что класс 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 );


1
Own non-overridden instance method/getter / not possible by intention unless using some workaround--- Это очень жаль. На мой взгляд, это недостаток ES6 +. Может быть , он должен быть обновлен , чтобы просто в виду method- то есть method.call(this). Лучше чем Foo.prototype.method. Babel / др. можно реализовать с помощью NFE (выражение именованной функции).
Рой Тинкер

method.call( this )является вероятным решением, за исключением того, что methodоно не привязано к желаемому базовому «классу» и, следовательно, не может быть переопределенным методом / получателем экземпляра . Таким способом всегда можно работать с независимыми от класса методами. Тем не менее, я не думаю, что современный дизайн настолько плох. В контексте объектов класса, производных от вашего базового класса Foo, могут быть веские причины для переопределения метода экземпляра. Этот переопределенный метод может иметь веские причины вызывать его superреализацию или нет. Любой случай приемлем и должен соблюдаться. Иначе это закончилось бы плохим дизайном ООП.
Томас Урбан

Несмотря на сахар ООП, методы ES все еще являются функциями , и люди захотят использовать и ссылаться на них как таковые. Моя проблема с синтаксисом класса ES заключается в том, что он не предоставляет прямой ссылки на выполняемый в настоящее время метод - что-то, что раньше было легко с помощью arguments.calleeили NFE.
Рой Тинкер,

Похоже, плохая практика или, по крайней мере, плохой дизайн программного обеспечения в любом случае. Я бы посчитал обе точки зрения противоречащими друг другу, так как не вижу приемлемой причины в контексте парадигмы ООП, которая предполагает доступ к вызываемому в настоящее время методу по ссылке (который не является только его контекстом, доступным через this). Это звучит как попытка смешать преимущества арифметики указателей голого C с C # более высокого уровня. Просто из любопытства: что бы вы использовали arguments.calleeв чисто разработанном ООП-коде?
Томас Урбан

Я работаю в большом проекте, построенном с помощью системы классов Dojo, которая позволяет вызывать реализацию (и) суперкласса (ов) текущего метода через this.inherited(currentFn, arguments);- где currentFnссылка на текущую выполняемую функцию. Невозможность напрямую ссылаться на выполняемую в настоящее время функцию делает его немного затруднительным в TypeScript, который использует синтаксис своего класса из ES6.
Рой Тинкер

20

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


3
Но статические функции не являются переопределенными методами-членами? Обычно вы пытаетесь не ссылаться на какие-либо переопределенные вещи статически.
Берги

1
@Bergi Я не уверен, что понимаю, на что вы указываете, но я столкнулся с одним конкретным случаем - с моделями гидратации MVC. Подклассы, расширяющие модель, могут захотеть реализовать статическую гидратную функцию. Однако, когда они жестко запрограммированы, экземпляры базовой модели возвращаются только когда-либо. Это довольно специфический пример, но многие шаблоны, которые полагаются на статическую коллекцию зарегистрированных экземпляров, будут затронуты этим. Один большой отказ от ответственности заключается в том, что мы пытаемся симулировать классическое наследование здесь, а не наследование прототипа ... И это не популярно: P
Эндрю Одри

Да, как я теперь заключил в своем собственном ответе, это даже не решается последовательно в «классическом» наследовании - иногда вы можете захотеть переопределения, иногда нет. Первая часть моего комментария указывала на статические функции класса, которые я не считал «членами». Лучше не обращайте на это внимания :-)
Берги
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.