Ruft den Klassennamen der ES6-Klasseninstanz ab

156

Gibt es harmonische Möglichkeiten, den Klassennamen von der ES6-Klasseninstanz abzurufen? Außer

someClassInstance.constructor.name

Derzeit zähle ich auf die Implementierung von Traceur. Und es scheint, dass Babel eine Polyfüllung hat, Function.nameTraceur nicht.

Um es zusammenzufassen: In ES6 / ES2015 / Harmony gab es keinen anderen Weg, und in ES.Next wird kein Geldautomat erwartet.

Es kann nützliche Muster für nicht minimierte serverseitige Anwendungen bereitstellen, ist jedoch in Anwendungen für Browser / Desktop / Mobile unerwünscht.

Babel verwendetcore-js zum Polyfill Function.name, es sollte für Traceur- und TypeScript-Anwendungen entsprechend manuell geladen werden.

Estus Flask
quelle
2
Ich bin auf dasselbe Problem gestoßen; Für Traceur bestand die einzige Lösung darin, den eigentlichen Klassencode selbst zu analysieren, um den Namen zu extrahieren, was meiner Meinung nach nicht als harmonisch gilt. Ich schluckte nur die Pille und wechselte zu Babel. Traceurs Entwicklung scheint etwas zu stagnieren, und viele ES6-Funktionen sind schlecht implementiert. Wie Sie bereits erwähnt haben, instance.constructor.nameund class.namegeben Sie den Klassennamen in ES6 zurück.
Andrew Odri
Scheint der einzige Weg zu sein.
Frederik Krautwald
Ist das im ES6-Standard?
Drudru
12
Es ist erwähnenswert, dass dies someClassInstance.constructor.nameentstellt wird, wenn Sie Ihren Code hässlich machen.
JamesB
stackoverflow.com/questions/2648293/… Vielleicht möchten Sie sich das ansehen, sollte funktionieren mit .constructor.
Florrie

Antworten:

204

someClassInstance.constructor.nameist genau der richtige Weg, dies zu tun. Transpiler unterstützen dies möglicherweise nicht, aber es ist der Standardweg gemäß der Spezifikation. (Die nameEigenschaft von Funktionen, die über ClassDeclaration-Produktionen deklariert wurden , wird in 14.5.15 , Schritt 6 festgelegt.)

Domenic
quelle
2
Davor hatte ich Angst. Kennen Sie eine vernünftige Polyfüllung dafür? Ich habe versucht herauszufinden, wie Babel das macht, hatte aber nur sehr wenig Erfolg.
Estus Flask
Ich weiß nicht genau, was Sie unter einer Polyfüllung für ein Sprachmerkmal (Klassen) verstehen.
Domenic
Ich meine die Polyfüllung für Konstruktor.Name. Es scheint, dass Babel es implementiert hat, aber es ist mir nicht gelungen zu verstehen, wie es das macht.
Estus Flask
2
@estus someClassInstance.constructorist eine Funktion. Alle Funktionen haben eine nameEigenschaft, die auf ihren Namen festgelegt ist. Deshalb muss babel nichts tun. Bitte sehen Sie developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Esteban
2
@Esteban Es scheint, dass Babel Core-js-Polyfills (einschließlich einer Polyfill für Function.name) dauerhaft fördert. Aus diesem Grund funktionieren einige Babel-Builds möglicherweise sofort in allen Browsern.
Estus Flask
52

Wie @Domenic sagt, verwenden Sie someClassInstance.constructor.name. @Esteban erwähnt in den Kommentaren, dass

someClassInstance.constructorist eine Funktion. Alle Funktionen haben eine nameEigenschaft ...

Um statisch auf den Klassennamen zuzugreifen, gehen Sie wie folgt vor (dies funktioniert übrigens mit meiner Babel-Version. Gemäß den Kommentaren zu @Domenic kann Ihr Kilometerstand variieren).

class SomeClass {
  constructor() {}
}

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

Aktualisieren

Babel ging es gut, aber Uglify / Minification verursachte mir Probleme. Ich mache ein Spiel und erstelle einen Hash gepoolter Sprite-Ressourcen (wobei der Schlüssel der Funktionsname ist). Nach der Minimierung wurde jede Funktion / Klasse benannt t. Dies tötet den Hash. Ich verwende es Gulpin diesem Projekt und nachdem ich die gulp-uglify-Dokumente gelesen habe, habe ich festgestellt, dass es einen Parameter gibt, der verhindert, dass diese lokale Variable / Funktionsname beschädigt wird. Also habe ich mich in meinem Gulpfile verändert

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

Hier gibt es einen Kompromiss zwischen Leistung und Lesbarkeit. Wenn die Namen nicht entstellt werden, führt dies zu einer (geringfügig) größeren Build-Datei (mehr Netzwerkressourcen) und möglicherweise zu einer langsameren Codeausführung (Zitieren erforderlich - möglicherweise BS). Wenn ich es jedoch gleich halten würde, müsste ich es getClassNamefür jede ES6-Klasse manuell definieren - auf statischer und Instanzebene. Nein Danke!

Aktualisieren

Nach der Diskussion in den Kommentaren scheint .namees ein gutes Paradigma zu sein , die Konvention zugunsten der Definition dieser Funktionen zu vermeiden . Es dauert nur wenige Codezeilen und ermöglicht eine vollständige Minimierung und Allgemeingültigkeit Ihres Codes (falls in einer Bibliothek verwendet). Ich denke, ich ändere meine Meinung und werde getClassNamemeine Klassen manuell definieren . Danke @estus! . Getter / Setter sind normalerweise eine gute Idee im Vergleich zum direkten Variablenzugriff, insbesondere in einer Client-basierten Anwendung.

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.
quelle
3
Das vollständige Deaktivieren des Mangelns ist keine sehr gute Idee, da das Mangeln viel zur Minimierung beiträgt. Es ist auch keine sehr gute Idee, das Thema zuerst im clientseitigen Code zu verwenden, aber die Klassen können mit der reservedOption Uglify vor Verfälschungen geschützt werden (eine Liste der Klassen kann mit regulärem Ausdruck oder was auch immer abgerufen werden).
Estus Flask
Sehr richtig. Es gibt einen Kompromiss zwischen einer größeren Datei. Auf diese Weise können Sie RegEx verwenden, um zu verhindern, dass nur ausgewählte Elemente beschädigt werden . Was meinst du damit, "ist es auch keine sehr gute Idee, das Thema im clientseitigen Code zu verwenden"? Würde es in einigen Szenarien ein Sicherheitsrisiko darstellen?
James L.
1
Nein, genau das, was bereits gesagt wurde. Es ist normal, dass clientseitiges JS minimiert wird, daher ist bereits bekannt, dass dieses Muster Probleme für die App verursacht. Eine zusätzliche Codezeile für die Klassenzeichenfolgenkennung anstelle eines sauberen nameMusters kann nur eine Win-Win-Situation sein. Das Gleiche kann auf Node angewendet werden, jedoch in geringerem Maße (z. B. verschleierte Elektronen-App). Als Faustregel würde ich mich auf den nameServercode verlassen, aber nicht auf den Browsercode und nicht auf die gemeinsame Bibliothek.
Estus Flask
OK, Sie empfehlen daher, zwei getClassName-Funktionen (statisch und instanziell) manuell zu definieren, um die Hölle zu umgehen und eine vollständige Minimierung zu ermöglichen (ohne störende RegEx). Dieser Punkt über die Bibliothek macht sehr viel Sinn. Macht sehr viel Sinn. Für mich ist mein Projekt eine selbst erstellte kleine Cordova-App, das sind also keine wirklichen Probleme. Alles darüber hinaus kann ich sehen, dass diese Probleme auftauchen. Danke für die Diskussion! Wenn Ihnen Verbesserungen am Beitrag einfallen, können Sie diese jederzeit bearbeiten.
James L.
1
Ja, ich habe ursprünglich nameDRYer-Code verwendet, um den Namen der Entität, die die Klasse (Dienst, Plugin usw.) verwendet, aus dem Klassennamen zu extrahieren, aber am Ende habe ich festgestellt, dass sie explizit mit static prop ( id, _name) dupliziert wird. ist nur der solideste Ansatz. Eine gute Alternative besteht darin, sich nicht um den Klassennamen zu kümmern, eine Klasse selbst (Funktionsobjekt) als Bezeichner für eine Entität zu verwenden, die dieser Klasse zugeordnet ist, und importsie, wo immer dies erforderlich ist (ein Ansatz, der von Angular 2 DI verwendet wurde).
Estus Flask
8

Abrufen des Klassennamens direkt von der Klasse

In früheren Antworten wurde erklärt, dass dies einwandfrei someClassInstance.constructor.namefunktioniert. Wenn Sie jedoch den Klassennamen programmgesteuert in eine Zeichenfolge konvertieren müssen und keine Instanz dafür erstellen möchten, denken Sie daran:

typeof YourClass === "function"

Und da jede Funktion eine nameEigenschaft hat, können Sie auch eine Zeichenfolge mit Ihrem Klassennamen abrufen:

YourClass.name

Was folgt, ist ein gutes Beispiel dafür, warum dies nützlich ist.

Laden von Webkomponenten

Wie aus der MDN-Dokumentation hervorgeht , laden Sie eine Webkomponente folgendermaßen:

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

Wo YourComponentist , die sich eine Klasse aus HTMLElement. Da es als bewährte Methode angesehen wird, die Klasse Ihrer Komponente nach dem Komponenten-Tag selbst zu benennen, wäre es hilfreich, eine Hilfsfunktion zu schreiben, mit der sich alle Ihre Komponenten registrieren könnten. Hier ist also diese Funktion:

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

Alles was Sie tun müssen ist:

registerComponent(YourComponent);

Das ist schön, weil es weniger fehleranfällig ist, als das Komponenten-Tag selbst zu schreiben. Zum Abschluss ist dies die upperCamelCaseToSnakeCase()Funktion:

// 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
quelle
Vielen Dank. Das Beispiel ist clientseitig. Wie bereits erwähnt, gibt es einige Probleme bei der Verwendung von Funktionsnamen in Browsern. Es wird erwartet, dass fast jeder Code des Browsers minimiert wird, aber dies ruiniert den Code, der auf dem Funktionsnamen basiert.
Estus Flask
Ja, du hast vollkommen recht. Der Minifier müsste so konfiguriert sein, dass er Klassennamen nicht berührt, damit dieser Ansatz funktioniert.
Lucio Paiva
1

Zur Babeltranspilation (vor der Minifizierung)

Wenn Sie Babel mit verwenden @babel/preset-env, ist es möglich, Klassendefinitionen beizubehalten, ohne sie in Funktionen zu konvertieren (wodurch das entfernt wirdconstructor Eigenschaft entfernt wird).

Sie können einige alte Browserkompatibilitäten mit dieser Konfiguration in Ihrem Verzeichnis löschen babel.config / babelrc:

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

Weitere Informationen zu targets: https://babeljs.io/docs/en/babel-preset-env#targets

Zur Babelminimierung (nach Transpilation)

Es sieht so aus, als ob es momentan keine einfache Lösung gibt ... Wir müssen uns die Ausschlüsse von Verstümmelungen ansehen.

Wenn nicht
quelle
Können Sie weiter erklären, wie dies bei der Minimierung helfen soll? class Foo {}wird auf so etwas wie class a{}mit jedem Ziel minimiert . Der Foominimierte Quellcode enthält kein Wort.
Estus Flask
Ehrlich gesagt habe ich nicht mehr als die Dokumentationen und die Tatsache, dass es mir bei der Verwendung dieser Konfiguration geholfen hat ... Ich verwende ECSY in einem babel-transpilierten Projekt und benötige diesen Parameter, um gültige Klassennamen zu erhalten: github.com/MozillaReality/ecsy/issues/ 119
Ifnot
Aha. Dies ist sehr spezifisch für den Code, mit dem Sie sich befasst haben. Beispielsweise können Namen für ES-Module und ES6 export class Foo{}beibehalten werden, da sie nicht weiter effizient hässlich gemacht werden können. Dies kann jedoch an anderen Stellen anders sein. Wir können nicht genau wissen, wie, ohne eine gute Vorstellung davon zu haben, was mit bestimmten Codeteilen zur Erstellungszeit geschieht. Auf jeden Fall hat sich dies seit 2015 nicht geändert. Für einige Kompilierungskonfigurationen und Code war dies immer möglich. Und ich würde sagen, diese Möglichkeit ist immer noch zu fragil, um Klassennamen für die App-Logik zu verwenden. Der Klassenname, auf den Sie sich verlassen, kann nach einer versehentlichen Änderung des Quellcodes zu Müll werden
Estus Flask
1
Ok, ich habe herausgefunden, was passiert, indem ich mir den Code angesehen habe. Meine Lösung behebt die Transpilation von Klassen in Funktionen. So hilft es vor der Minifizierung. Aber nicht mit dem Minimierungsproblem. Ich muss weiter graben, weil ich nicht verstehen konnte, wie mein constructor.namegesamter Code in der minimierten Version noch funktioniert ... unlogisch: /
Ifnot