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

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

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

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

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

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

person simonzack    schedule 20.02.2015    source источник
comment
SomeObject.print кажется естественным. Но this.n внутри не имеет смысла, поскольку нет экземпляра, если мы говорим о статических методах.   -  person dfsq    schedule 20.02.2015
comment
Однако @dfsq printN не статичен.   -  person simonzack    schedule 20.02.2015
comment
Вы правы, запутанные имена.   -  person dfsq    schedule 20.02.2015
comment
Мне любопытно, почему у этого вопроса не так много голосов! Разве это не обычная практика для создания служебных функций?   -  person Thoran    schedule 01.12.2015


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

person Bergi    schedule 21.02.2015
comment
Можете ли вы объяснить свойство конструктора и определение метода класса? - person Chris; 26.04.2016
comment
@Chris: Каждый класс является функцией-конструктором (точно так же, как вы знаете его из ES5 без синтаксиса class), нет никакой разницы в определении метода. Все зависит от того, как вы это найдете, через унаследованное свойство constructor или непосредственно по его имени. - person Bergi; 26.04.2016
comment
Другой пример - поздние статические привязки PHP. This.constructor не только уважает наследование, но также помогает избежать обновления кода при изменении имени класса. - person ricanontherun; 19.07.2018
comment
@ricanontherun Необходимость обновлять код при изменении имен переменных не является аргументом против использования имен. Также инструменты рефакторинга в любом случае могут делать это автоматически. - person Bergi; 19.07.2018
comment
Как это реализовать в машинописном тексте? Выдает ошибку Property 'staticProperty' does not exist on type 'Function' - person ayZagen; 28.03.2019
comment
@ayZagen Я не знаю, я думаю, вам следует задать новый вопрос о том, как для этого вводить TypeScript, и опубликовать не- рабочий код там. - person Bergi; 28.03.2019

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

Виды доступа

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

Затем доступ

  • from static method/getter of Foo
    • 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
    • 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);

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

Фон

  • When in context of an instance's method or getter
    • this is referring to current instance.
    • super в основном относится к одному и тому же экземпляру, но в некоторой степени относится к методам и геттерам, написанным в контексте некоторого текущего класса, расширяемого (с использованием прототипа прототипа Foo).
    • определение класса экземпляра, используемого при его создании, доступно в this.constructor.
  • When in context of a static method or getter there is no "current instance" by intention and so
    • this is available to refer to the definition of current class directly.
    • 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 );

person Thomas Urban    schedule 29.04.2017
comment
Own non-overridden instance method/getter / not possible by intention unless using some workaround --- Какая жалость. На мой взгляд, это недостаток ES6 +. Возможно, его следует обновить, чтобы можно было просто ссылаться на method, то есть method.call(this). Лучше, чем Foo.prototype.method. Вавилон / и т. Д. может быть реализовано с использованием NFE (именованного функционального выражения). - person Roy Tinker; 05.10.2017
comment
method.call( this ) - вероятное решение, за исключением того, что method тогда не привязан к желаемому базовому классу и, следовательно, не может быть непереопределяемым методом / получателем экземпляра. Таким образом всегда можно работать с методами, не зависящими от классов. Тем не менее, я не думаю, что нынешний дизайн так уж плох. В контексте объектов класса, производного от вашего базового класса Foo, могут быть веские причины переопределить метод экземпляра. У этого метода переопределения могут быть веские причины вызывать свою super реализацию или нет. Любой случай приемлем и должен соблюдаться. В противном случае это закончится плохим дизайном ООП. - person Thomas Urban; 17.10.2017
comment
Несмотря на сахар ООП, методы ES по-прежнему являются функциями, и люди захотят использовать их и ссылаться на них как на таковые. Моя проблема с синтаксисом класса ES заключается в том, что он не дает прямой ссылки на выполняемый в данный момент метод - что-то, что раньше было легко с помощью arguments.callee или NFE. - person Roy Tinker; 26.10.2017
comment
В любом случае это звучит как плохая практика или, по крайней мере, плохой дизайн программного обеспечения. Я бы рассмотрел обе точки зрения, противоречащие друг другу, поскольку я не вижу подходящей причины в контексте парадигмы ООП, которая включает доступ к вызываемому в данный момент методу по ссылке (это не только его контекст, доступный через this). Это похоже на попытку смешать преимущества арифметики указателей чистого C с более высокоуровневым C #. Просто из любопытства: что бы вы использовали arguments.callee в чисто разработанном ООП-коде? - person Thomas Urban; 27.11.2017
comment
Я работаю в большом проекте, построенном с помощью системы классов Dojo, которая позволяет вызывать реализацию (-и) суперкласса (ов) текущего метода через this.inherited(currentFn, arguments);, где currentFn - это ссылка на текущую выполняемую функцию. Отсутствие возможности напрямую ссылаться на выполняемую в данный момент функцию делает его немного «волосатым» в TypeScript, который заимствует синтаксис класса из ES6. - person Roy Tinker; 27.11.2017
comment
Я понимаю! В основном это происходит из-за разнородных установок, требующих смешивания старого мира с новым. Я сам использовал аналогичный подход в проекте, и ссылка на текущий метод действительно требуется для обхода цепочки прототипов, чтобы предотвратить бесконечные регрессии при поиске следующей перегруженной версии. Однако это прошлое в чисто проекте ES6. Итак, пока я придерживаюсь старого кода в старых проектах или пытаюсь наследовать от классов старого стиля и начинаю использовать обработку наследования ES6 только в таких производных классах, хотя я не знаю специфики классов Dojo, поэтому не могу сказать по поводу твоего дела. - person Thomas Urban; 28.11.2017

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

person Andrew Odri    schedule 20.02.2015
comment
Но статическая функция - это не переопределенные методы-члены? Обычно вы пытаетесь не ссылаться на какие-либо переопределенные данные статически. - person Bergi; 21.02.2015
comment
@Bergi Я не уверен, что понимаю, на что вы указываете, но один конкретный случай, с которым я столкнулся, связан с шаблонами гидратации модели MVC. Подклассы, расширяющие модель, могут захотеть реализовать статическую функцию гидратации. Однако, когда они жестко запрограммированы, экземпляры базовой модели возвращаются только когда-либо. Это довольно конкретный пример, но многие шаблоны, которые полагаются на наличие статической коллекции зарегистрированных экземпляров, будут затронуты этим. Один большой отказ от ответственности заключается в том, что мы пытаемся смоделировать здесь классическое наследование, а не прототипное наследование ... И это не популярно: P - person Andrew Odri; 24.02.2015
comment
Да, как я заключил сейчас в своем собственном ответе, это даже не решается последовательно в классическом наследовании - иногда вам могут понадобиться переопределения, иногда нет. Первая часть моего комментария указала на функции статического класса, которые я не считал членами. Лучше не обращайте на это внимания :-) - person Bergi; 24.02.2015