Uzyskaj nazwę klasy instancji klasy ES6

157

Czy są jakieś „harmonijne” sposoby uzyskania nazwy klasy z instancji klasy ES6? Inny niż

someClassInstance.constructor.name

Obecnie liczę na wdrożenie Traceur. I wydaje się, że Babel ma polyfill, Function.namepodczas gdy Traceur nie.

Podsumowując: w ES6 / ES2015 / Harmony nie było innej drogi, aw ES nie oczekuje się ATM. Dalej.

Może dostarczać użytecznych wzorców dla niezminimalizowanych aplikacji po stronie serwera, ale jest niepożądany w aplikacjach przeznaczonych dla przeglądarek / komputerów stacjonarnych / urządzeń przenośnych.

Babel używacore-js do wypełniania Function.name, powinien być ładowany ręcznie dla aplikacji Traceur i TypeScript, jeśli jest to właściwe.

Estus Flask
źródło
2
Natknąłem się na ten sam problem; dla Traceur jedynym rozwiązaniem było przeanalizowanie samego kodu klasy w celu wyodrębnienia nazwy, której nie uważam za harmonijną. Po prostu połknąłem pigułkę i przeszedłem na Babel; Rozwój Traceur wydaje się być nieco w stagnacji, a wiele funkcji ES6 jest słabo zaimplementowanych. Jak wspomniano, instance.constructor.namei class.namezwróci nazwę klasy w odpowiednim ES6.
Andrew Odri
Wydaje się, że to jedyny sposób.
Frederik Krautwald
Czy to jest w standardzie ES6?
drudru
12
Warto wspomnieć, że someClassInstance.constructor.namezostanie zniekształcony, jeśli zmienisz kod.
JamesB
stackoverflow.com/questions/2648293/ ... Może zechcesz się temu przyjrzeć, powinno działać w / .constructor.
Florrie

Odpowiedzi:

205

someClassInstance.constructor.namejest dokładnie właściwym sposobem, aby to zrobić. Transpilery mogą tego nie obsługiwać, ale jest to standardowy sposób w specyfikacji. ( nameWłaściwość funkcji zadeklarowanych przez produkcje ClassDeclaration jest ustawiana w 14.5.15 , krok 6.)

Domenic
źródło
2
Tego się bałem. Czy znasz jakieś rozsądne wypełnienie? Próbowałem dowiedzieć się, jak to robi Babel, ale odniosłem niewielki sukces.
Estus Flask
Naprawdę nie wiem, co masz na myśli, mówiąc o polyfill dla funkcji języka (zajęć).
Domenic
Mam na myśli wypełnienie dla constructor.name. Wygląda na to, że Babel go zaimplementował, ale nie udało mi się zrozumieć, jak to robi.
Estus Flask
2
@estus someClassInstance.constructorto funkcja. Wszystkie funkcje mają namewłaściwość, która jest ustawiona na nazwę. Dlatego babel nie musi nic robić. Zobacz developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Esteban
2
@Esteban Wygląda na to, że Babel uporczywie promuje wypełniacze core-js (w tym polyfill dla Function.name), dlatego niektóre kompilacje Babel mogą działać po wyjęciu z pudełka we wszystkich przeglądarkach.
Estus Flask
52

Jak mówi @Domenic, użyj someClassInstance.constructor.name. @Esteban wspomina w komentarzach, że

someClassInstance.constructorjest funkcją. Wszystkie funkcje mają namewłaściwość ...

Tak więc, aby uzyskać statyczny dostęp do nazwy klasy, wykonaj następujące czynności (przy okazji działa to z moją wersją Babel. Zgodnie z komentarzami na @Domenic Twój przebieg może się różnić).

class SomeClass {
  constructor() {}
}

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

Aktualizacja

Babel był w porządku, ale uglifikacja / minifikacja ostatecznie spowodowała problemy. Tworzę grę i tworzę skrót zasobów Sprite w puli (gdzie kluczem jest nazwa funkcji). Po minifikacji każda funkcja / klasa została nazwana t. To zabija haszysz. Używam Gulpw tym projekcie i po przeczytaniu dokumentacji gulp-uglify odkryłem, że istnieje parametr zapobiegający zmianie lokalnej nazwy zmiennej / funkcji. Więc zmieniłem się w moim pliku łykowym

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

Występuje tutaj kompromis między wydajnością a czytelnością. Brak zniekształcenia nazw spowoduje (nieco) większy plik kompilacji (więcej zasobów sieciowych) i potencjalnie wolniejsze wykonanie kodu (potrzebne źródło - może to być BS). Z drugiej strony, gdybym zachował to samo, musiałbym ręcznie zdefiniować getClassNamedla każdej klasy ES6 - na poziomie statycznym i instancji. Nie, dziękuję!

Aktualizacja

Po dyskusji w komentarzach wydaje się, że unikanie .namekonwencji na rzecz definiowania tych funkcji to dobry paradygmat. Zajmuje tylko kilka wierszy kodu i pozwoli na pełną minifikację i ogólność kodu (jeśli jest używany w bibliotece). Więc chyba zmienię zdanie i ręcznie zdefiniuję getClassNamena moich zajęciach. Dzięki @estus! . Getter / Setters i tak są zwykle dobrym pomysłem w porównaniu do bezpośredniego dostępu do zmiennych, zwłaszcza w aplikacji opartej na kliencie.

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.
źródło
3
Całkowite wyłączenie zniekształcenia nie jest dobrym pomysłem, ponieważ zniekształcanie bardzo przyczynia się do minifikacji. Nie jest też dobrym pomysłem używanie tematu w kodzie po stronie klienta w pierwszej kolejności, ale klasy można zabezpieczyć przed reservedzniekształceniem za pomocą opcji Uglify (listę klas można uzyskać za pomocą regexp lub cokolwiek innego).
Estus Flask
Bardzo prawdziwe. Istnieje kompromis w postaci większego rozmiaru pliku. Wygląda na to, że możesz użyć wyrażenia regularnego, aby zapobiec zniekształcaniu tylko dla wybranych elementów . Co masz na myśli, mówiąc, że „użycie tematu w kodzie po stronie klienta również nie jest dobrym pomysłem”? Czy stanowiłoby to zagrożenie bezpieczeństwa w niektórych scenariuszach?
James L.,
1
Nie, tylko to, co już zostało powiedziane. Minimalizacja kodu JS po stronie klienta jest normalna, więc już wiadomo, że ten wzorzec spowoduje problemy w aplikacji. Dodatkowa linia kodu dla identyfikatora ciągu klasy zamiast zgrabnego namewzorca może być po prostu korzystna dla obu stron. To samo można zastosować do Node, ale w mniejszym stopniu (np. Zaciemniona aplikacja Electron). Z reguły oparłbym się na namekodzie serwera, ale nie na kodzie przeglądarki i nie na wspólnej bibliotece.
Estus Flask
OK, więc zalecamy ręczne zdefiniowanie 2 funkcji getClassName (statycznych i instancji), aby ominąć piekło i pozwolić na pełną minifikację (bez irytującego RegEx). Ta kwestia dotycząca biblioteki ma wiele sensu. To ma dużo sensu. Dla mnie mój projekt to mała aplikacja Cordova wykonana samodzielnie, więc nie są to tak naprawdę problemy. Cokolwiek poza tym, widzę, jak te problemy się pojawiają. Dzięki za dyskusję! Jeśli możesz wymyślić jakieś ulepszenia postu, nie krępuj się go edytować.
James L.
1
Tak, początkowo nameużyłem kodu DRYer do wyodrębnienia nazwy jednostki używającej klasy (usługi, wtyczki itp.) Z nazwy klasy, ale w końcu odkryłem, że kopiuje ją jawnie za pomocą statycznej właściwości ( id, _name) jest po prostu najbardziej solidnym podejściem. Dobrą alternatywą jest nie przejmowanie się nazwą klasy, używanie samej klasy (obiektu funkcji) jako identyfikatora dla jednostki, która jest mapowana na tę klasę i importwszędzie tam, gdzie jest to potrzebne (podejście, które było używane przez Angular 2 DI).
Estus Flask
8

Pobieranie nazwy klasy bezpośrednio z klasy

Poprzednie odpowiedzi wyjaśniły, że to someClassInstance.constructor.namedziała dobrze, ale jeśli chcesz programowo przekonwertować nazwę klasy na ciąg i nie chcesz tworzyć instancji tylko do tego, pamiętaj, że:

typeof YourClass === "function"

A ponieważ każda funkcja ma namewłaściwość, innym dobrym sposobem na uzyskanie ciągu znaków z nazwą klasy jest wykonanie:

YourClass.name

Poniżej znajduje się dobry przykład tego, dlaczego jest to przydatne.

Ładowanie komponentów internetowych

Jak uczy dokumentacja MDN , ładowanie komponentu internetowego jest następujące:

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

Skąd YourComponentpochodzi klasa HTMLElement. Ponieważ nazywanie klasy komponentu po samym tagu komponentu jest uważane za dobrą praktykę, dobrze byłoby napisać funkcję pomocniczą, której wszystkie komponenty mogłyby się zarejestrować. Oto ta funkcja:

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

Więc wszystko, co musisz zrobić, to:

registerComponent(YourComponent);

Co jest miłe, ponieważ jest mniej podatne na błędy niż samodzielne pisanie tagu komponentu. Podsumowując, oto upperCamelCaseToSnakeCase()funkcja:

// 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
źródło
Dzięki. Przykład jest po stronie klienta. Jak już wspomniano, występują problemy z używaniem nazw funkcji w przeglądarkach. Prawie każdy fragment kodu przeglądarki powinien zostać zminimalizowany, ale zrujnuje to kod, który opiera się na nazwie funkcji.
Estus Flask
Tak, masz całkowitą rację. Minifier musiałby być skonfigurowany tak, aby nie dotykał nazw klas, aby to podejście działało.
Lucio Paiva,
1

Do transpilacji babel (przed minifikacją)

Jeśli używasz Babel z @babel/preset-env, możliwe jest zachowanie definicji klas bez konwertowania ich na funkcje (co usuwa constructorwłaściwość)

Możesz zrezygnować ze starej zgodności przeglądarki z tą konfiguracją w babel.config / babelrc:

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

Więcej informacji na temat targets: https://babeljs.io/docs/en/babel-preset-env#targets

Do minifikacji babel (po transpilacji)

Wygląda na to, że w tej chwili nie ma łatwego rozwiązania ... Musimy przyjrzeć się zniekształconym wykluczeniom.

Jeśli nie
źródło
Czy możesz dalej wyjaśnić, w jaki sposób ma to pomóc w minifikacji? class Foo {}zostanie zminimalizowany do czegoś podobnego class a{}do dowolnego celu. W Foozminimalizowanym kodzie źródłowym nie będzie słowa.
Estus Flask
Szczerze mówiąc, nie przekopałem się więcej niż dokumentacja i fakt, że pomogło mi to w użyciu tej konfiguracji ... Używam ECSY w projekcie babel transpiled i wymaga tego parametru, aby uzyskać prawidłowe nazwy klas: github.com/MozillaReality/ecsy/issues/ 119
Ifnot
Widzę. Jest to bardzo specyficzne dla kodu, z którym miałeś do czynienia. Np. Nazwy mogą zostać zachowane dla modułów ES i ES6, ponieważ export class Foo{}nie mogą być dalej skutecznie zniekształcone, ale w innych miejscach może być inaczej, nie możemy wiedzieć, jak dokładnie, nie mając dobrego pojęcia, co dzieje się z określonymi fragmentami kodu w czasie kompilacji. W każdym razie nie zmieniło się to od 2015 roku. Zawsze było to możliwe dla niektórych konfiguracji i kodu kompilacji. I powiedziałbym, że ta możliwość jest nadal zbyt delikatna, aby używać nazw klas w logice aplikacji. Nazwa klasy, na której polegasz, może stać się śmieciami po jednej przypadkowej zmianie w kodzie źródłowym
Estus Flask
1
Ok, znalazłem, co się dzieje, patrząc na kod. Moje rozwiązanie naprawia transpilację klas do funkcji. Więc to pomaga przed minifikacją. Ale nie z problemem minifikacji. Muszę kontynuować kopanie, ponieważ nie mogłem zrozumieć, jak cały mój kod używany constructor.namenadal działa w zminimalizowanej wersji ... nielogiczne: /
Ifnot