Dapatkan nama kelas instance kelas ES6

157

Apakah ada cara 'harmonis' untuk mendapatkan nama kelas dari instance kelas ES6? Selain daripada

someClassInstance.constructor.name

Saat ini saya mengandalkan implementasi Traceur. Dan sepertinya Babel memiliki polyfill untuk Function.namesementara Traceur tidak.

Singkatnya: tidak ada cara lain di ES6 / ES2015 / Harmony, dan tidak ada ATM yang diharapkan di ES.Berikutnya.

Ini mungkin menyediakan pola yang berguna untuk aplikasi sisi server yang tidak dijinakkan tetapi tidak diinginkan dalam aplikasi yang dimaksudkan untuk browser / desktop / seluler.

Babel menggunakancore-js untuk polyfill Function.name, itu harus dimuat secara manual untuk aplikasi Traceur dan TypeScript yang sesuai.

Estus Flask
sumber
2
Saya menemukan masalah yang sama; untuk Traceur satu-satunya solusi adalah mengurai kode kelas aktual itu sendiri untuk mengekstrak namanya, yang menurut saya tidak memenuhi syarat sebagai harmonis. Saya baru saja menelan pil dan beralih ke Babel; Perkembangan Traceur tampaknya agak stagnan, dan banyak fitur ES6 diimplementasikan dengan buruk. Seperti yang Anda sebutkan, instance.constructor.namedan class.namekembalikan nama kelas dalam ES6 yang tepat.
Andrew Odri
Tampaknya menjadi satu-satunya cara.
Frederik Krautwald
Apakah ini dalam standar ES6?
drudru
12
Perlu disebutkan bahwa someClassInstance.constructor.nameakan hancur jika Anda mengubah kode Anda.
JamesB
stackoverflow.com/questions/2648293/… Mungkin ingin melihat ini, harus bekerja dengan / .constructor.
Florrie

Jawaban:

206

someClassInstance.constructor.namepersis cara yang tepat untuk melakukan ini. Transpiler mungkin tidak mendukung ini, tetapi itu adalah cara standar per spesifikasi. ( nameProperti fungsi yang dideklarasikan melalui produksi ClassDeclaration diatur dalam 14.5.15 , langkah 6.)

Domenik
sumber
2
Itu yang saya takutkan. Apakah Anda mengetahui adanya polyfill yang masuk akal untuk itu? Saya sudah mencoba mencari tahu bagaimana Babel melakukan itu tetapi hanya sedikit keberhasilan.
Estus Flask
Saya tidak benar-benar tahu apa yang Anda maksud dengan polyfill untuk fitur bahasa (kelas).
Domenic
Maksud saya polyfill untuk constructor.name. Tampaknya Babel telah menerapkannya, tetapi saya tidak berhasil memahami bagaimana hal itu dilakukan.
Estus Flask
2
@estus someClassInstance.constructoradalah Fungsi. Semua Fungsi memiliki nameproperti yang disetel sesuai namanya. Itu sebabnya babel tidak perlu melakukan apa pun. Silakan lihat developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Esteban
2
@Esteban Tampaknya Babel secara terus-menerus mempromosikan polyfill core-js (termasuk polyfill untuk Function.name), itulah mengapa beberapa build Babel dapat bekerja di luar kotak di semua browser.
Estus Flask
53

Seperti kata @Domenic, gunakan someClassInstance.constructor.name. @Esteban menyebutkan dalam komentar itu

someClassInstance.constructoradalah sebuah Fungsi. Semua Fungsi memiliki nameproperti ...

Jadi, untuk mengakses nama kelas secara statis, lakukan hal berikut (ini bekerja dengan BTW versi Babel saya. Menurut komentar pada @Domenic, jarak tempuh Anda mungkin berbeda).

class SomeClass {
  constructor() {}
}

var someClassInstance = new SomeClass();
someClassInstance.constructor.name;      // === 'SomeClass'
SomeClass.name                           // === 'SomeClass'

Memperbarui

Babel baik-baik saja, tetapi uglify / minification akhirnya menyebabkan saya bermasalah. Saya membuat game, dan saya sedang membuat hash sumber Sprite dikumpulkan (di mana kuncinya adalah nama fungsi). Setelah minifikasi, setiap fungsi / kelas diberi nama t. Ini membunuh hash. Saya menggunakan Gulpdalam proyek ini, dan setelah membaca dokumen gulp-uglify saya menemukan ada parameter untuk mencegah ini variabel / fungsi nama mangling terjadi. Jadi, dalam gulpfile saya, saya berubah

.pipe($.uglify()) untuk .pipe($.uglify({ mangle: false }))

Ada trade-off kinerja vs keterbacaan di sini. Tidak mengacaukan nama-nama akan menghasilkan (sedikit) membangun file yang lebih besar (lebih banyak sumber daya jaringan) dan berpotensi eksekusi kode lebih lambat (kutipan diperlukan - mungkin BS). Di sisi lain, jika saya menyimpannya sama saya harus mendefinisikan secara manual getClassNamepada setiap kelas ES6 - pada tingkat statis dan contoh. Tidak, terima kasih!

Memperbarui

Setelah diskusi dalam komentar, sepertinya menghindari .namekonvensi yang mendukung mendefinisikan fungsi-fungsi tersebut adalah paradigma yang baik. Hanya membutuhkan beberapa baris kode, dan akan memungkinkan minifikasi penuh dan generalisasi kode Anda (jika digunakan di perpustakaan). Jadi saya kira saya berubah pikiran dan secara manual akan menentukan getClassNamekelas saya. Terima kasih @estus! . Getter / Setters biasanya merupakan ide yang bagus dibandingkan dengan akses variabel langsung, terutama dalam aplikasi berbasis klien.

class SomeClass {
  constructor() {}
  static getClassName(){ return 'SomeClass'; }
  getClassName(){ return SomeClass.getClassName(); }
}
var someClassInstance = new SomeClass();
someClassInstance.constructor.getClassName();      // === 'SomeClass' (static fn)
someClassInstance.getClassName();                  // === 'SomeClass' (instance fn)
SomeClass.getClassName()                           // === 'SomeClass' (static fn)
James L.
sumber
3
Menonaktifkan mangling sepenuhnya bukan ide yang sangat bagus karena mangling berkontribusi banyak terhadap minifikasi. Bukan ide yang baik untuk menggunakan subjek dalam kode sisi-klien di tempat pertama, tetapi kelas-kelas dapat dilindungi dari mangling dengan reservedopsi Uglify (daftar kelas dapat diperoleh dengan regexp atau apa pun).
Estus Flask
Sangat benar. Ada trade-off memiliki ukuran file yang lebih besar. Sepertinya ini adalah bagaimana Anda dapat menggunakan RegEx untuk mencegah mangling hanya untuk item tertentu . Apa maksud Anda "bukan ide yang baik untuk menggunakan subjek dalam kode sisi klien"? Apakah akan menimbulkan risiko keamanan dalam beberapa skenario?
James L.
1
Tidak, hanya apa yang sudah dikatakan. Adalah normal bagi JS sisi klien untuk dikecilkan, sehingga sudah diketahui bahwa pola ini akan menyebabkan masalah bagi aplikasi. Ekstra satu baris kode untuk pengenal string kelas dan bukan namepola yang rapi mungkin hanya win-win Hal yang sama dapat diterapkan pada Node, tetapi pada tingkat yang lebih rendah (misalnya aplikasi Electron dikaburkan). Sebagai aturan praktis, saya akan bergantung pada namekode server, tetapi tidak dalam kode browser dan tidak di perpustakaan umum.
Estus Flask
OK jadi Anda merekomendasikan untuk mendefinisikan 2 fungsi getClassName secara manual (statis & instance) untuk menyiasati neraka & untuk memungkinkan minifikasi penuh (tanpa RegEx yang mengganggu). Poin tentang perpustakaan itu sangat masuk akal. Masuk akal. Bagi saya, proyek saya adalah aplikasi Cordova kecil buatan sendiri, jadi itu bukan masalah. Apa pun di luar itu saya bisa melihat masalah ini naik. Terima kasih untuk diskusi! Jika Anda dapat memikirkan perbaikan apa pun pada kiriman, silakan mengeditnya.
James L.
1
Ya, saya awalnya menggunakan namekode DRYer untuk mengekstrak nama entitas yang menggunakan kelas (layanan, plugin, dll) dari nama kelas, tetapi pada akhirnya saya menemukan bahwa itu menduplikasinya secara eksplisit dengan prop statis ( id, _name) hanyalah pendekatan yang paling solid. Alternatif yang baik adalah tidak peduli dengan nama kelas, gunakan kelas itu sendiri (objek fungsi) sebagai pengidentifikasi untuk entitas yang dipetakan ke kelas ini dan di importmana pun diperlukan (pendekatan yang digunakan oleh Angular 2 DI).
Estus Flask
8

Mendapatkan nama kelas langsung dari kelas

Jawaban sebelumnya menjelaskan bahwa itu someClassInstance.constructor.nameberfungsi dengan baik, tetapi jika Anda secara terprogram mengubah nama kelas menjadi string dan tidak ingin membuat turunan hanya untuk itu, ingat bahwa:

typeof YourClass === "function"

Dan, karena setiap fungsi memiliki nameproperti, cara lain yang bagus untuk mendapatkan string dengan nama kelas Anda adalah dengan hanya melakukan:

YourClass.name

Berikut ini adalah contoh bagus mengapa ini berguna.

Memuat komponen web

Seperti yang diajarkan oleh dokumentasi MDN kepada kami, ini adalah cara Anda memuat komponen web:

customElements.define("your-component", YourComponent);

Di mana YourComponentkelas membentang dari HTMLElement. Karena dianggap praktik yang baik untuk memberi nama kelas komponen Anda setelah tag komponen itu sendiri, alangkah baiknya menulis fungsi pembantu yang dapat digunakan semua komponen Anda untuk mendaftar sendiri. Jadi, inilah fungsi itu:

function registerComponent(componentClass) {
    const componentName = upperCamelCaseToSnakeCase(componentClass.name);
    customElements.define(componentName, componentClass);
}

Jadi yang perlu Anda lakukan adalah:

registerComponent(YourComponent);

Yang bagus karena lebih sedikit kesalahan daripada menulis tag komponen sendiri. Untuk menyelesaikannya, ini adalah upperCamelCaseToSnakeCase()fungsinya:

// converts `YourString` into `your-string`
function upperCamelCaseToSnakeCase(value) {
    return value
        // first char to lower case
        .replace(/^([A-Z])/, $1 => $1.toLowerCase())
        // following upper chars get preceded with a dash
        .replace(/([A-Z])/g, $1 => "-" + $1.toLowerCase());
}
Lucio Paiva
sumber
Terima kasih. Contohnya adalah sisi klien. Seperti yang telah disebutkan, ada beberapa masalah dengan menggunakan nama fungsi di browser. Hampir setiap potongan kode browser diharapkan diperkecil tetapi ini akan merusak kode yang bergantung pada nama fungsi.
Estus Flask
Ya, Anda benar sekali. Pengukur min harus dikonfigurasikan untuk tidak menyentuh nama kelas agar pendekatan ini berfungsi.
Lucio Paiva
1

Untuk transpilasi babel (sebelum minifikasi)

Jika Anda menggunakan Babel dengan @babel/preset-env, dimungkinkan untuk menjaga definisi kelas tanpa mengubahnya menjadi fungsi (yang menghapus constructorproperti)

Anda dapat menjatuhkan beberapa kompatibilitas browser lama dengan konfigurasi ini di babel.config / babelrc:

{
  "presets": [
    ["@babel/preset-env", {"targets": {"browsers": ["> 2%"]}}]
  ]
}

Informasi lebih lanjut tentang targets: https://babeljs.io/docs/en/babel-preset-env#targets

Untuk minifikasi babel (setelah transpilasi)

Sepertinya tidak ada solusi mudah saat ini ... Kita perlu melihat pengecualian yang membingungkan.

Jika tidak
sumber
Bisakah Anda jelaskan lebih lanjut bagaimana ini seharusnya membantu dengan minifikasi? class Foo {}akan diperkecil menjadi sesuatu seperti class a{}dengan target apa pun. Tidak akan ada Fookata dalam kode sumber yang diperkecil.
Estus Flask
Jujur saya tidak menggali lebih dari dokumentasi dan fakta itu membantu saya menggunakan konfigurasi ini ... Saya menggunakan ECSY ke proyek babel yang ditransformasikan dan memerlukan param ini untuk mendapatkan nama kelas yang valid: github.com/MozillaReality/ecsy/issues/ 119
Ifnot
Saya melihat. Ini sangat spesifik untuk kode yang Anda tangani. Misalnya nama dapat disimpan untuk modul ES dan ES6 karena export class Foo{}tidak dapat secara efisien dijelaskan lebih lanjut tetapi ini mungkin berbeda di tempat lain, kita tidak dapat mengetahui bagaimana tepatnya tanpa memiliki ide yang baik apa yang terjadi dengan potongan kode tertentu pada waktu pembuatan. Bagaimanapun, ini tidak berubah sejak 2015. Itu selalu mungkin untuk beberapa konfigurasi kompilasi dan kode. Dan saya katakan kemungkinan ini masih terlalu rapuh untuk menggunakan nama kelas untuk logika aplikasi. Nama kelas yang Anda andalkan dapat menjadi sampah setelah satu perubahan kode sumber yang tidak
disengaja
1
Ok saya menemukan apa yang terjadi dengan melihat kode. Solusi saya memperbaiki pemindahan kelas ke fungsi. Jadi ada baiknya sebelum minifikasi. Tetapi tidak dengan masalah minifikasi. Saya harus terus menggali karena, saya tidak dapat memahami bagaimana semua kode saya menggunakan constructor.namemasih berfungsi dalam versi yang
diperkecil