Wywołaj metody statyczne ze zwykłych metod klasy ES6


176

Jaki jest standardowy sposób wywoływania metod statycznych? Mogę myśleć o użyciu constructorlub używaniu samej nazwy klasy, nie podoba mi się ta druga, ponieważ nie wydaje mi się to konieczne. Czy ten pierwszy jest zalecanym sposobem, czy jest coś innego?

Oto (wymyślony) przykład:

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

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

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

8
SomeObject.printczuje się naturalnie. Ale this.nwnętrze nie ma sensu, ponieważ nie ma instancji, jeśli mówimy o metodach statycznych.
dfsq

3
@dfsq printNnie jest jednak statyczne.
simonzack,

Masz rację, pomieszane imiona.
dfsq

1
Ciekaw jestem, dlaczego to pytanie nie ma tylu głosów za! Czy nie jest to powszechna praktyka tworzenia funkcji narzędziowych?
Thoran

Odpowiedzi:


211

Oba sposoby są opłacalne, ale robią różne rzeczy, jeśli chodzi o dziedziczenie z zastąpioną metodą statyczną. Wybierz tego, którego zachowania oczekujesz:

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

Odwoływanie się do właściwości statycznej za pośrednictwem klasy będzie w rzeczywistości statyczne i stale będzie dawać tę samą wartość. Za pomocąthis.constructor zamiast tego spowoduje użycie dynamicznego wysyłania i odniesienie do klasy bieżącego wystąpienia, w którym właściwość statyczna może mieć odziedziczoną wartość, ale może również zostać zastąpiona.

Jest to zgodne z zachowaniem języka Python, w którym można wybrać odwołanie do właściwości statycznych za pośrednictwem nazwy klasy lub instancji self .

Jeśli oczekujesz, że właściwości statyczne nie będą zastępowane (i zawsze odwołują się do jednej z bieżącej klasy), tak jak w Javie , użyj jawnego odwołania.


czy możesz wyjaśnić właściwość konstruktora w porównaniu z definicją metody klasy?
Chris

2
@Chris: Każda klasa jest funkcją konstruktora (tak jak ją znasz z ES5 bez classskładni), nie ma różnicy w definicji metody. To tylko kwestia tego, jak to sprawdzisz, poprzez dziedziczoną constructorwłaściwość lub bezpośrednio po jej nazwie.
Bergi

Innym przykładem jest późne statyczne powiązania PHP . This.constructor nie tylko szanuje dziedziczenie, ale także pomaga uniknąć konieczności aktualizowania kodu w przypadku zmiany nazwy klasy.
ricanontherun

@ricanontherun Konieczność aktualizowania kodu podczas zmiany nazw zmiennych nie jest argumentem przeciwko używaniu nazw. Również narzędzia do refaktoryzacji mogą to robić automatycznie.
Bergi

Jak to zaimplementować w maszynopisie? Daje błądProperty 'staticProperty' does not exist on type 'Function'
ayZagen

72

Natknąłem się na ten wątek, szukając odpowiedzi na podobny przypadek. Zasadniczo można znaleźć wszystkie odpowiedzi, ale nadal trudno jest wydobyć z nich najważniejsze informacje.

Rodzaje dostępu

Załóżmy, że klasa Foo prawdopodobnie pochodzi z jakiejś innej (ych) klasy (klas) z prawdopodobnie większą liczbą klas pochodnych.

Następnie uzyskujesz dostęp

  • z metody statycznej / pobierającej Foo
    • niektóre prawdopodobnie nadpisały statyczną metodę / getter:
      • this.method()
      • this.property
    • jakaś prawdopodobnie nadpisana metoda instancji / getter:
      • niemożliwe z założenia
    • własna nieprzesłonięta metoda statyczna / pobierająca:
      • Foo.method()
      • Foo.property
    • własna nieprzesłonięta metoda instancji / getter:
      • niemożliwe z założenia
  • z metody instancji / pobierającej Foo
    • niektóre prawdopodobnie nadpisały statyczną metodę / getter:
      • this.constructor.method()
      • this.constructor.property
    • jakaś prawdopodobnie nadpisana metoda instancji / getter:
      • this.method()
      • this.property
    • własna nieprzesłonięta metoda statyczna / pobierająca:
      • Foo.method()
      • Foo.property
    • własna nieprzesłonięta metoda instancji / getter:
      • nie jest możliwe celowo, chyba że zastosowano jakieś obejście :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Należy pamiętać, że używanie thisnie działa w ten sposób, gdy używa się funkcji strzałkowych lub wywołuje metody / metody pobierające jawnie powiązane z wartością niestandardową.

tło

  • W kontekście metody instancji lub metody pobierającej
    • this odnosi się do bieżącej instancji.
    • super zasadniczo odnosi się do tej samej instancji, ale w pewnym stopniu adresuje metody i metody pobierające napisane w kontekście jakiejś klasy, która jest obecnie rozszerzana (przy użyciu prototypu prototypu Foo).
    • definicja klasy instancji użyta do jej utworzenia jest dostępna na this.constructor.
  • Kiedy w kontekście metody statycznej lub metody pobierającej, celowo nie ma „bieżącej instancji” i tak dalej
    • this jest dostępny, aby bezpośrednio odwołać się do definicji bieżącej klasy.
    • super nie odnosi się również do jakiejś instancji, ale do statycznych metod i metod pobierających napisanych w kontekście jakiejś klasy, która jest obecnie rozszerzana.

Wniosek

Wypróbuj ten kod:

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--- To prawdziwa szkoda. Moim zdaniem to wada ES6 +. Może powinien zostać zaktualizowany, aby umożliwić proste odwoływanie się do method- tj method.call(this). Lepiej niż Foo.prototype.method. Babel / itp. można zaimplementować używając NFE (nazwane wyrażenie funkcyjne).
Roy Tinker

method.call( this )jest prawdopodobnym rozwiązaniem, z wyjątkiem tego, że methodnie jest wówczas powiązany z żądaną podstawową „klasą”, a zatem nie jest nieprzesłoniętą metodą instancji / pobierającą . W ten sposób zawsze można pracować z metodami niezależnymi od klas. Niemniej jednak nie sądzę, aby obecny projekt był taki zły. W kontekście obiektów klasy wywodzącej się z klasy bazowej Foo mogą istnieć dobre powody do przesłonięcia metody instancji. Ta zastąpiona metoda może mieć dobre powody, aby wywołać jej superimplementację lub nie. Każdy przypadek jest kwalifikowalny i należy go przestrzegać. W przeciwnym razie skończyłoby się to złym projektem OOP.
Thomas Urban

Pomimo cukru OOP, metody ES nadal funkcjonują , a ludzie będą chcieli używać ich i odnosić się do nich jako takich. Mój problem ze składnią klasy ES polega na tym, że nie zawiera ona bezpośredniego odniesienia do aktualnie wykonywanej metody - coś, co kiedyś było łatwe za pośrednictwem arguments.calleelub NFE.
Roy Tinker

Brzmi jak zła praktyka lub przynajmniej zły projekt oprogramowania. Uznałbym oba punkty widzenia za sprzeczne ze sobą, ponieważ nie widzę uzasadnionego powodu w kontekście paradygmatu OOP, który obejmuje dostęp do aktualnie wywoływanej metody przez odniesienie (które nie jest tylko jej kontekstem, który jest dostępny za pośrednictwem this). Brzmi to jak próba połączenia korzyści płynących z arytmetyki wskaźników czystego C z C # wyższego poziomu. Tak z ciekawości: do czego byś użył arguments.calleew czysto zaprojektowanym kodzie OOP?
Thomas Urban

Pracuję w dużym projekcie zbudowanym w oparciu o system klas Dojo, który umożliwia wywołanie implementacji (y) nadklasy (y) bieżącej metody poprzez this.inherited(currentFn, arguments);- gdzie currentFnjest odniesieniem do aktualnie wykonywanej funkcji. Brak możliwości bezpośredniego odniesienia się do aktualnie wykonywanej funkcji sprawia, że ​​jest ona trochę zawiły w TypeScript, który bierze składnię klasy z ES6.
Roy Tinker

20

Jeśli planujesz zrobić jakikolwiek spadek, to polecam this.constructor. Ten prosty przykład powinien zilustrować, dlaczego:

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()zaloguje ConstructorSuper Hello ConstructorSuper!się do konsoli
  • test2.callPrint()zaloguje ConstructorSub Hello ConstructorSub!się do konsoli

Nazwana klasa nie poradzi sobie dobrze z dziedziczeniem, chyba że jawnie przedefiniujesz każdą funkcję, która odwołuje się do nazwanej Class. Oto przykład:

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()zaloguje NamedSuper Hello NamedSuper!się do konsoli
  • test4.callPrint()zaloguje NamedSuper Hello NamedSub!się do konsoli

Zobacz wszystkie powyższe uruchomione w Babel REPL .

Widać z tego, że test4nadal uważa, że ​​jest to super klasa; w tym przykładzie może się wydawać, że nie jest to wielka sprawa, ale jeśli próbujesz odwoływać się do funkcji składowych, które zostały nadpisane lub do nowych zmiennych składowych, znajdziesz się w tarapatach.


3
Ale funkcje statyczne nie są przesłoniętymi metodami składowymi? Zazwyczaj próbujesz nie odnosić się statycznie do żadnych nadpisanych elementów.
Bergi

1
@Bergi Nie jestem pewien, czy rozumiem, na co zwracasz uwagę, ale jeden konkretny przypadek, z którym się spotkałem, dotyczy wzorców nawodnienia modelu MVC. Podklasy rozszerzające model mogą chcieć zaimplementować statyczną funkcję hydratowania. Jednak gdy są one zakodowane na stałe, zwracane są tylko wystąpienia modelu podstawowego. Jest to dość specyficzny przykład, ale wiele wzorców, które opierają się na posiadaniu statycznej kolekcji zarejestrowanych instancji, zostałoby przez to dotkniętych. Jednym wielkim zastrzeżeniem jest to, że próbujemy zasymulować dziedziczenie klasyczne zamiast dziedziczenia prototypowego ... I to nie jest popularne: P
Andrew Odri

Tak, jak doszedłem do wniosku w mojej własnej odpowiedzi, nie jest to nawet konsekwentnie rozwiązywane w dziedziczeniu „klasycznym” - czasami możesz chcieć nadpisać, a czasami nie. Pierwsza część mojego komentarza dotyczyła statycznych funkcji klas, których nie uważałem za „składowe”. Lepiej to zignoruj ​​:-)
Bergi
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.