Ottieni il nome della classe dell'istanza della classe ES6


157

Esistono modi "armoniosi" per ottenere il nome della classe dall'istanza della classe ES6? Altro che

someClassInstance.constructor.name

Attualmente conto sull'implementazione di Traceur. E sembra che Babel abbia un polifill per Function.namementre Traceur no.

Riassumendo: in ES6 / ES2015 / Harmony non c'era altro modo e in ES non si prevede nulla di ATM.

Può fornire schemi utili per applicazioni lato server non minime, ma è indesiderato in applicazioni pensate per browser / desktop / dispositivi mobili.

Babel usa ilcore-js polyfill Function.name, dovrebbe essere caricato manualmente per le applicazioni Traceur e TypeScript come appropriato.


2
Mi sono imbattuto nello stesso problema; per Traceur l'unica soluzione era analizzare il codice della classe stessa per estrarne il nome, che non credo sia considerato armonioso. Ho appena ingoiato la pillola e sono passato a Babele; Lo sviluppo di Traceur sembra in qualche modo stagnante e molte funzionalità di ES6 sono implementate in modo inadeguato. Come hai detto, instance.constructor.namee class.namerestituisci il nome della classe in ES6 corretto.
Andrew Odri,

Sembra essere l'unico modo.
Frederik Krautwald,

È questo nello standard ES6?
drudru,

12
Vale la pena ricordare che someClassInstance.constructor.namesi altererà se si brucia il codice.
JamesB,

stackoverflow.com/questions/2648293/… Potrebbe voler guardare questo, dovrebbe funzionare w / .constructor.
Florrie,

Risposte:


206

someClassInstance.constructor.nameè esattamente il modo corretto per farlo. I transpiler potrebbero non supportare questo, ma è il modo standard secondo le specifiche. (La nameproprietà delle funzioni dichiarate tramite produzioni ClassDeclaration è impostata in 14.5.15 , passaggio 6.)


2
Questo è ciò di cui avevo paura. Sei a conoscenza di qualche polifill ragionevole per questo? Ho provato a capire come Babel lo fa, ma ha avuto ben poco successo.
Estus Flask

Non so davvero cosa intendi per polyfill per una funzione linguistica (classi).
Domenic,

Intendo il polyfill per constructor.name. Sembra che Babel l'abbia implementato, ma non sono riuscito a capire come lo fa.
Estus Flask,

2
@estus someClassInstance.constructorè una funzione. Tutte le funzioni hanno una nameproprietà impostata sul suo nome. Ecco perché Babel non deve fare nulla. Si prega di consultare developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Esteban

2
@Esteban Sembra che Babel promuova costantemente i polyfill core-js (incluso un polyfill per Function.name), ecco perché alcuni build Babel possono funzionare immediatamente in tutti i browser.
Estus Flask il

53

Come dice @Domenic, usa someClassInstance.constructor.name. @Esteban menziona nei commenti che

someClassInstance.constructorè una funzione. Tutte le funzioni hanno una nameproprietà ...

Quindi, per accedere staticamente al nome della classe, procedi come segue (questo funziona con la mia versione di Babel BTW. Secondo i commenti su @Domenic il tuo chilometraggio può variare).

class SomeClass {
  constructor() {}
}

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

Aggiornare

Babele stava bene, ma l'ubriacatura / minificazione finì per causarmi problemi. Sto creando un gioco e sto creando un hash di risorse Sprite in pool (dove la chiave è il nome della funzione). Dopo la minificazione, ogni funzione / classe è stata nominata t. Questo uccide l'hash. Sto usando Gulpin questo progetto e dopo aver letto i documenti gulp-uglify ho scoperto che esiste un parametro per impedire che questa variabile locale / nome mangling si verifichi. Quindi, nel mio gulpfile sono cambiato

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

C'è un compromesso tra prestazioni e leggibilità qui. Non alterare i nomi comporterà un file di build (leggermente) più grande (più risorse di rete) e un'esecuzione del codice potenzialmente più lenta (citazione necessaria - potrebbe essere BS). D'altra parte, se lo mantenessi lo stesso, dovrei definire manualmente getClassNamesu ogni classe ES6, a livello statico e di istanza. No grazie!

Aggiornare

Dopo la discussione nei commenti, sembra che evitare la .nameconvenzione a favore della definizione di tali funzioni sia un buon paradigma. Richiede solo poche righe di codice e consentirà la completa minimizzazione e generalità del codice (se utilizzato in una libreria). Quindi immagino di cambiare idea e di definire manualmente le getClassNamemie lezioni. Grazie @estus! . I getter / setter sono di solito una buona idea rispetto all'accesso diretto variabile comunque, specialmente in un'applicazione client-based.

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)

3
Disabilitare completamente il mangling non è una buona idea perché il mangling contribuisce molto alla minimizzazione. Non è nemmeno una buona idea utilizzare l'oggetto nel codice lato client, in primo luogo, ma le classi possono essere protette dalla reservedmanipolazione con l'opzione Uglify (un elenco di classi può essere ottenuto con regexp o altro).
Estus Flask

Verissimo. C'è un compromesso con dimensioni di file maggiori. Questo è il modo in cui è possibile utilizzare RegEx per impedire la manipolazione solo per gli elementi selezionati . Cosa intendi con "non è una buona idea utilizzare l'oggetto nel codice lato client"? Presenterebbe un rischio per la sicurezza in alcuni scenari?
James L.

1
No, proprio quello che è stato già detto. È normale che JS sul lato client sia minimizzato, quindi è già noto che questo modello causerà problemi all'app. Una riga di codice aggiuntiva per l'identificatore di stringa di classe invece di un namemodello pulito potrebbe essere semplicemente vantaggiosa. La stessa cosa può essere applicata a Nodo, ma in misura minore (ad es. App Electron offuscata). Come regola generale, mi affiderei al namecodice del server, ma non al codice del browser e non alla libreria comune.
Estus Flask,

OK, quindi ti consigliamo di definire manualmente 2 funzioni getClassName (statiche e di istanza) per aggirare l'inferno mangling e consentire la minificazione completa (senza un fastidioso RegEx). Quel punto sulla biblioteca ha molto senso. Ha molto senso. Per me, il mio progetto è una piccola app Cordova fatta da sé, quindi quelli non sono davvero problemi. Qualunque cosa oltre a ciò, posso vedere sollevare questi problemi. Grazie per la discussione! Se riesci a pensare a qualche miglioramento al post, sentiti libero di modificarlo.
James L.,

1
Sì, inizialmente ho usato il namecodice DRYer per estrarre il nome dell'entità che utilizza la classe (servizio, plugin, ecc.) Dal nome della classe, ma alla fine ho scoperto che lo duplicava esplicitamente con static prop ( id, _name) è solo l'approccio più solido. Una buona alternativa è non preoccuparsi del nome della classe, utilizzare una classe stessa (oggetto funzione) come identificatore per un'entità che è mappata su questa classe e importovunque sia necessario (un approccio che è stato utilizzato da Angular 2 DI).
Estus Flask,

8

Ottenere il nome della classe direttamente dalla classe

Le risposte precedenti hanno spiegato che someClassInstance.constructor.namefunziona bene, ma se è necessario convertire a livello di codice il nome della classe in una stringa e non si desidera creare un'istanza solo per quello, ricordare che:

typeof YourClass === "function"

E poiché ogni funzione ha una nameproprietà, un altro modo carino per ottenere una stringa con il nome della tua classe è semplicemente fare:

YourClass.name

Quello che segue è un buon esempio del perché questo è utile.

Caricamento dei componenti Web

Come ci insegna la documentazione MDN , ecco come caricare un componente Web:

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

Da dove YourComponentsi estende una classe HTMLElement. Dato che è buona norma nominare la classe del componente in base al tag del componente stesso, sarebbe utile scrivere una funzione di supporto che tutti i componenti potrebbero utilizzare per registrarsi. Quindi ecco quella funzione:

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

Quindi tutto ciò che devi fare è:

registerComponent(YourComponent);

Il che è bello perché è meno soggetto a errori rispetto alla scrittura del tag componente da soli. Per concludere, questa è la upperCamelCaseToSnakeCase()funzione:

// 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());
}

Grazie. L'esempio è lato client. Come già accennato, ci sono alcuni problemi con l'uso dei nomi delle funzioni nei browser. Quasi ogni parte del codice del browser dovrebbe essere minimizzata, ma ciò rovinerà il codice che si basa sul nome della funzione.
Estus Flask,

Sì, hai perfettamente ragione. Il minificatore dovrebbe essere configurato in modo da non toccare i nomi delle classi perché questo approccio funzioni.
Lucio Paiva,

1

Per la transpilazione della babele (prima della minificazione)

Se stai usando Babel con @babel/preset-env, è possibile mantenere le definizioni delle classi senza convertirle in funzioni (che rimuove la constructorproprietà)

Puoi eliminare la vecchia compatibilità del browser con questa configurazione nel tuo babel.config / babelrc:

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

Maggiori informazioni su targets: https://babeljs.io/docs/en/babel-preset-env#targets

Per la minificazione della babele (dopo la transpilazione)

Sembra che non ci sia una soluzione facile in questo momento ... Dobbiamo guardare alle esclusioni mangling.


Puoi spiegare ulteriormente come questo dovrebbe aiutare con la minificazione? class Foo {}sarà ridotto a qualcosa come class a{}con qualsiasi bersaglio. Non ci sarà Fooparola nel codice sorgente minimizzato.
Estus Flask

Onestamente non ho scavato più delle documentazioni e del fatto che mi ha aiutato a usare questa configurazione ... Sto usando ECSY nel progetto traspilato da babele e richiede questo parametro per ottenere nomi di classe validi: github.com/MozillaReality/ecsy/issues/ 119
Ifnot

Vedo. Questo è molto specifico per il codice che hai trattato. Ad esempio, i nomi possono essere conservati per i moduli ES e ES6 perché export class Foo{}non possono essere ulteriormente migliorati in modo efficiente, ma questo può essere diverso in altri luoghi, non possiamo sapere esattamente senza avere una buona idea di cosa succede con specifici pezzi di codice in fase di costruzione. In ogni caso, questo non è cambiato dal 2015. Era sempre possibile per alcune configurazioni e codici di compilazione. E direi che questa possibilità è ancora troppo fragile per usare i nomi delle classi per la logica delle app. Il nome della classe su cui fai affidamento potrebbe diventare spazzatura dopo una modifica accidentale del codice sorgente
Estus Flask,

1
Ok ho trovato cosa sta succedendo guardando il codice. La mia soluzione corregge il trasferimento delle classi alle funzioni. Quindi aiuta prima della minificazione. Ma non con il problema di minificazione. Devo continuare a scavare perché, non riuscivo a capire come tutto il mio codice usando constructor.nameancora funzioni nella versione minimizzata ... illogico: /
Ifnot
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.