Викличте статичні методи із звичайних методів класу 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'
ayZagen

72

Я наткнувся на цю нитку, шукаючи відповіді на подібний випадок. В основному всі відповіді знайдені, але все-таки важко витягти з них сутнісне.

Види доступу

Припустимо, клас Foo, ймовірно, походить від якогось іншого класу, і, ймовірно, більше класів, похідних від нього.

Тоді звернення

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

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

Фон

  • Коли в контексті методу або отримувача екземпляра
    • this посилається на поточний екземпляр.
    • super в основному посилається на той самий екземпляр, але дещо адресація методів та гетерів, написаних у контексті поточного класу, розширюється (використовуючи прототип прототипу Фоо).
    • визначення класу екземпляра, що використовується при його створенні, доступне в 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. Вавилон / тощо. можна реалізувати, використовуючи NFE (ім'я вираження функції).
Рой Тінкер

method.call( this )є ймовірним рішенням, за винятком того method, що не прив'язаний до бажаного базового "класу", і тому не може бути методом / getter, що не переосмислюється . Завжди завжди можна працювати з незалежними від класу методами. Тим не менш, я не думаю, що сучасний дизайн такий поганий. У контексті об'єктів класу, похідних від вашого базового класу Foo, можуть бути вагомі причини переоцінити метод екземпляра. Цей метод, що переосмислюється, може мати вагомі причини закликати його superвпровадити чи ні. Будь-яка справа є прийнятною і її слід дотримуватися. Інакше це закінчиться поганим дизайном OOP.
Томас Урбан

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

Схоже, погана практика чи принаймні погана розробка програмного забезпечення. Я б вважав, що обидві точки зору суперечать одна одній, оскільки я не бачу придатних причин у контексті парадигми OOP, яка передбачає доступ до поточного методу за допомогою посилання (що не є лише його контекстом, як це доступно через this). Це схоже на те, що намагаються поєднати переваги арифметики вказівника голого C із C # вищого рівня. Лише з цікавості: для чого б ви використали arguments.calleeчітко розроблений код OOP?
Томас Урбан

Я працюю у великому проекті, побудованому за системою класів 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.