Panggil metode statis dari metode kelas ES6 biasa

176

Apa cara standar untuk memanggil metode statis? Saya bisa memikirkan menggunakan constructoratau menggunakan nama kelas itu sendiri, saya tidak suka yang terakhir karena merasa tidak perlu. Apakah yang pertama direkomendasikan, atau ada yang lain?

Berikut ini contoh (buat-buat):

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

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

  printN(){
    this.constructor.print(this.n);
  }
}
simonzack
sumber
8
SomeObject.printterasa alami. Tetapi this.ndi dalam tidak masuk akal karena tidak ada contoh, jika kita berbicara tentang metode statis.
dfsq
3
@dfsq printNtidak statis.
simonzack
Anda benar, nama yang membingungkan.
dfsq
1
Saya ingin tahu mengapa pertanyaan ini tidak memiliki banyak upvotes! Bukankah ini praktik umum untuk membuat fungsi utilitas?
Thoran

Jawaban:

211

Kedua cara itu layak, tetapi mereka melakukan hal-hal yang berbeda ketika menyangkut pewarisan dengan metode statis yang ditimpa. Pilih orang yang perilakunya Anda harapkan:

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

Mengacu pada properti statis melalui kelas akan benar-benar statis dan terus-menerus memberikan nilai yang sama. Menggunakan this.constructorsebaliknya akan menggunakan pengiriman dinamis dan merujuk ke kelas instance saat ini, di mana properti statis mungkin memiliki nilai yang diwarisi tetapi juga bisa diganti.

Ini cocok dengan perilaku Python, di mana Anda dapat memilih untuk merujuk ke properti statis baik melalui nama kelas atau instance self.

Jika Anda mengharapkan properti statis tidak akan ditimpa (dan selalu merujuk ke salah satu dari kelas saat ini), seperti di Jawa , gunakan referensi eksplisit.

Bergi
sumber
dapatkah Anda menjelaskan definisi konstruktor properti vs metode kelas?
Chris
2
@ Chris: Setiap kelas adalah fungsi konstruktor (seperti yang Anda tahu dari ES5 tanpa classsintaks), tidak ada perbedaan dalam definisi metode. Ini hanya masalah bagaimana Anda melihatnya, melalui properti yang diwarisiconstructor atau langsung dengan namanya.
Bergi
Contoh lain adalah Late Static Bindings PHP . Tidak hanya ini. Konstruktor menghormati warisan, tetapi juga membantu Anda menghindari harus memperbarui kode jika Anda mengubah nama kelas.
ricanontherun
@ricanontherun Harus memperbarui kode ketika Anda mengubah nama variabel bukan argumen yang menentang penggunaan nama. Alat refactoring juga dapat melakukannya secara otomatis.
Bergi
Bagaimana cara mengimplementasikan ini dalam naskah? Ini memberikan kesalahanProperty 'staticProperty' does not exist on type 'Function'
ayZagen
72

Saya menemukan thread ini mencari jawaban untuk kasus yang sama. Pada dasarnya semua jawaban ditemukan, tetapi masih sulit untuk mengekstrak esensi dari mereka.

Jenis Akses

Asumsikan kelas Foo mungkin berasal dari beberapa kelas lain dengan esensi lebih banyak dari kelas itu.

Kemudian mengakses

  • dari metode statis / pengambil Foo
    • beberapa metode / pengambil statis yang mungkin ditimpa :
      • this.method()
      • this.property
    • beberapa metode instance / pengambil yang mungkin ditimpa :
      • tidak mungkin dengan desain
    • memiliki metode / pengambil statis non-override :
      • Foo.method()
      • Foo.property
    • memiliki metode instance / pengambil non-override :
      • tidak mungkin dengan desain
  • dari metode instance / pengambil Foo
    • beberapa metode / pengambil statis yang mungkin ditimpa :
      • this.constructor.method()
      • this.constructor.property
    • beberapa metode instance / pengambil yang mungkin ditimpa :
      • this.method()
      • this.property
    • memiliki metode / pengambil statis non-override :
      • Foo.method()
      • Foo.property
    • memiliki metode instance / pengambil non-override :
      • tidak mungkin dengan niat kecuali menggunakan beberapa solusi :
        • Foo.prototype.method.call( this )
        • Object.getOwnPropertyDescriptor( Foo.prototype,"property" ).get.call(this);

Perlu diingat bahwa menggunakan thistidak berfungsi seperti ini saat menggunakan fungsi panah atau memanggil metode / getter secara eksplisit terikat dengan nilai kustom.

Latar Belakang

  • Ketika dalam konteks metode instance atau pengambil
    • this mengacu pada contoh saat ini.
    • super pada dasarnya mengacu pada contoh yang sama, tetapi sedikit menangani metode dan getter yang ditulis dalam konteks beberapa kelas saat ini sedang diperluas (dengan menggunakan prototipe prototipe Foo).
    • definisi kelas instance yang digunakan untuk membuatnya tersedia per this.constructor.
  • Ketika dalam konteks metode statis atau pengambil tidak ada "instance saat ini" dengan niat dan sebagainya
    • this tersedia untuk merujuk langsung ke definisi kelas saat ini.
    • super juga tidak merujuk pada beberapa instance, tetapi pada metode statis dan getter yang ditulis dalam konteks beberapa kelas saat ini sedang diperluas.

Kesimpulan

Coba kode ini:

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 );

Thomas Urban
sumber
1
Own non-overridden instance method/getter / not possible by intention unless using some workaround--- Sayang sekali. Menurut pendapat saya, ini adalah kekurangan ES6 +. Mungkin itu harus diperbarui untuk memungkinkan hanya merujuk ke method- yaitu method.call(this). Lebih baik dari Foo.prototype.method. Babel / dll. dapat diimplementasikan menggunakan NFE (bernama ekspresi fungsi).
Roy Tinker
method.call( this )adalah solusi yang mungkin kecuali untuk methodtidak terikat ke "kelas" pangkalan yang diinginkan kemudian dan dengan demikian gagal menjadi metode instance / pengambil non-override . Selalu mungkin untuk bekerja dengan metode independen kelas seperti itu. Meskipun demikian saya tidak berpikir desain saat ini seburuk itu. Dalam konteks objek kelas yang berasal dari kelas dasar Anda Foo mungkin ada alasan bagus untuk mengganti metode instance. Metode yang ditimpa mungkin memiliki alasan yang baik untuk meminta superpenerapannya atau tidak. Salah satu kasus memenuhi syarat dan harus dipatuhi. Kalau tidak, itu akan berakhir dengan desain OOP yang buruk.
Thomas Urban
Terlepas dari gula OOP, metode ES masih berfungsi , dan orang-orang akan ingin menggunakan dan merujuknya seperti itu. Masalah saya dengan sintaks kelas ES adalah tidak memberikan referensi langsung ke metode yang sedang dijalankan - sesuatu yang dulu mudah dilakukan melalui arguments.calleeatau NFE.
Roy Tinker
Kedengarannya seperti praktik yang buruk atau setidaknya desain perangkat lunak yang buruk pula. Saya menganggap kedua sudut pandang itu bertentangan satu sama lain, karena saya tidak melihat alasan yang memenuhi syarat dalam konteks paradigma OOP yang melibatkan pengaksesan metode yang saat ini dijalankan dengan referensi (yang bukan hanya konteksnya sebagaimana tersedia melalui this). Kedengarannya seperti mencoba mencampur manfaat pointer arithmetics dari bare C dengan level C # yang lebih tinggi. Karena penasaran: untuk apa Anda menggunakan arguments.calleekode OOP yang dirancang dengan rapi?
Thomas Urban
Saya bekerja di proyek besar yang dibangun dengan sistem kelas Dojo, yang memungkinkan memanggil implementasi superclass dari metode saat ini melalui this.inherited(currentFn, arguments);- di mana currentFnada referensi ke fungsi yang sedang dijalankan. Tidak dapat merujuk fungsi yang sedang dieksekusi secara langsung membuatnya sedikit berbulu di TypeScript, yang mengambil sintaks kelasnya dari ES6.
Roy Tinker
20

Jika Anda berencana melakukan warisan apa pun, maka saya akan merekomendasikan this.constructor. Contoh sederhana ini harus menggambarkan mengapa:

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()akan masuk ConstructorSuper Hello ConstructorSuper!ke konsol
  • test2.callPrint()akan masuk ConstructorSub Hello ConstructorSub!ke konsol

Kelas bernama tidak akan berurusan dengan warisan dengan baik kecuali jika Anda secara eksplisit mendefinisikan kembali setiap fungsi yang membuat referensi ke Kelas bernama. Berikut ini sebuah contoh:

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()akan masuk NamedSuper Hello NamedSuper!ke konsol
  • test4.callPrint()akan masuk NamedSuper Hello NamedSub!ke konsol

Lihat semua yang di atas berjalan di Babel REPL .

Anda dapat melihat dari ini yang test4masih berpikir itu di kelas super; dalam contoh ini mungkin tidak tampak seperti masalah besar, tetapi jika Anda mencoba untuk mereferensikan fungsi anggota yang telah ditimpa atau variabel anggota baru, Anda akan menemukan diri Anda dalam masalah.

Andrew Odri
sumber
3
Tetapi fungsi statis bukan metode anggota overridden? Biasanya Anda mencoba untuk tidak merujuk hal-hal yang ditimpa secara statis.
Bergi
1
@Bergi Saya tidak yakin saya mengerti apa yang Anda tunjukkan, tetapi satu kasus khusus yang saya temui adalah dengan pola hidrasi model MVC. Sub kelas yang memperluas model mungkin ingin menerapkan fungsi hidrat statis. Namun, ketika ini adalah hard-coded, contoh model dasar hanya pernah dikembalikan. Ini adalah contoh yang cukup spesifik, tetapi banyak pola yang mengandalkan koleksi statis dari contoh terdaftar akan dipengaruhi oleh ini. Salah satu penafian besar adalah bahwa kita mencoba mensimulasikan warisan klasik di sini, daripada warisan prototipal ... Dan itu tidak populer: P
Andrew Odri
Ya, seperti yang saya simpulkan sekarang dalam jawaban saya sendiri, ini bahkan tidak diselesaikan secara konsisten dalam warisan "klasik" - kadang-kadang Anda mungkin ingin ditimpa, kadang tidak. Bagian pertama dari komentar saya menunjuk pada fungsi kelas statis, yang saya tidak anggap sebagai "anggota". Lebih baik abaikan saja :-)
Bergi