Получить имя класса экземпляра класса ES6

157

Существуют ли «гармоничные» способы получения имени класса из экземпляра класса ES6? Кроме как

someClassInstance.constructor.name

В настоящее время я рассчитываю на реализацию Traceur. И кажется, что у Бабеля есть полифилл, Function.nameа у Трейсера нет.

Подводя итог, можно сказать, что в ES6 / ES2015 / Harmony другого пути не было, и в ES.Next ничего не ожидается.

Он может предоставлять полезные шаблоны для незавершенных серверных приложений, но нежелателен в приложениях, предназначенных для браузера / настольного компьютера / мобильного устройства.

Babel используетcore-js для заполнения Function.name, он должен быть загружен вручную для приложений Traceur и TypeScript в зависимости от ситуации.

Настой Эстус
источник
2
Я столкнулся с той же проблемой; для Traceur единственным решением было проанализировать сам код класса, чтобы извлечь имя, которое, я не думаю, квалифицируется как гармоничное. Я просто проглотил таблетку и переключился на Бабель; Развитие Traceur, кажется, несколько застойно, а многие функции ES6 плохо реализованы. Как уже упоминалось, instance.constructor.nameи class.nameвозвращает имя класса в надлежащем ES6.
Эндрю Одри
Кажется, единственный путь.
Фредерик Краутвальд
Это в стандарте ES6?
drudru
12
Стоит упомянуть, что это someClassInstance.constructor.nameбудет искажено, если вы унизите свой код.
ДжеймсБ
stackoverflow.com/questions/2648293/… Возможно, стоит посмотреть на это, должно работать без .constructor.
Флорри

Ответы:

206

someClassInstance.constructor.nameэто совершенно правильный способ сделать это. Транспортеры могут не поддерживать это, но это стандартный способ согласно спецификации. ( nameСвойство функций, объявленных через производства ClassDeclaration, устанавливается в 14.5.15 , шаг 6.)

Доменик
источник
2
Вот чего я боялся. Вы знаете какой-нибудь разумный polyfill для этого? Я пытался выяснить, как Бабел делает это, но имел очень небольшой успех.
Настой Эстус
Я действительно не знаю, что вы подразумеваете под polyfill для языковой функции (классов).
Доменик
Я имею в виду polyfill для constructor.name. Кажется, что Babel реализовал это, но мне не удалось понять, как это происходит.
Настой
2
@estus someClassInstance.constructor- это функция. У всех функций есть nameсвойство, для которого установлено его имя. Вот почему Бабел не нужно ничего делать. Пожалуйста, смотрите developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Esteban
2
@Esteban Похоже, что Babel постоянно продвигает полифилы core-js (включая полифилл для Function.name), поэтому некоторые сборки Babel могут работать "из коробки" во всех браузерах.
Настой Эстус
53

Как говорит @ Domenic, используйте someClassInstance.constructor.name. @Esteban упоминает в комментариях, что

someClassInstance.constructorявляется функцией Все функции имеют nameсвойство ...

Таким образом, чтобы получить статический доступ к имени класса, сделайте следующее (это работает с моей версией Babel, кстати. Согласно комментариям на @Domenic ваш пробег может отличаться).

class SomeClass {
  constructor() {}
}

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

Обновить

Вавилон был в порядке, но в конечном итоге у меня возникли проблемы. Я создаю игру и создаю хеш объединенных ресурсов Sprite (где ключ - имя функции). После минификации каждая функция / класс была названа t. Это убивает хэш. Я использую Gulpв этом проекте, и после прочтения документации gulp-uglify я обнаружил, что есть параметр, чтобы предотвратить искажение этой локальной переменной / имени функции. Итак, в моем gulpfile я изменился

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

Здесь есть компромисс между производительностью и удобочитаемостью. Отказ от именования приведет к (немного) большему файлу сборки (больше сетевых ресурсов) и потенциально к более медленному выполнению кода (требуется цитирование - может быть BS). С другой стороны, если бы я сохранил то же самое, мне пришлось бы вручную определять getClassNameвсе классы ES6 - на статическом уровне и на уровне экземпляра. Нет, спасибо!

Обновить

После обсуждения в комментариях кажется, что отказ от .nameсоглашения в пользу определения этих функций - хорошая парадигма. Это займет всего несколько строк кода и позволит полностью минимизировать и обобщать ваш код (если он используется в библиотеке). Так что, я думаю, я передумал и буду определять getClassNameна уроках вручную . Спасибо @estus! , Метод получения / установки обычно является хорошей идеей по сравнению с прямым доступом к переменным в любом случае, особенно в клиентском приложении.

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
Полное отключение искажения не очень хорошая идея, потому что искажение вносит большой вклад в минификацию. Во-первых, не очень хорошая идея использовать тему в клиентском коде, но классы могут быть защищены от искажения с reservedпомощью опции Uglify (список классов можно получить с помощью regexp или чего-либо еще).
Настой Эстус
Очень верно. Есть компромисс наличия большего размера файла. Похоже, это то, как вы можете использовать RegEx для предотвращения искажения только для выбранных элементов . Что вы имеете в виду, что «не очень хорошая идея использовать тему в клиентском коде»? Представит ли это угрозу безопасности в некоторых сценариях?
Джеймс Л.
1
Нет, только то, что уже было сказано. Обычно JS на стороне клиента минимизируется, поэтому уже известно, что этот шаблон вызовет проблемы для приложения. Дополнительная строка кода для идентификатора строки класса вместо аккуратного nameшаблона может быть просто беспроигрышной. То же самое можно применить к Node, но в меньшей степени (например, запутанное приложение Electron). Как правило, я полагаюсь на nameкод сервера, но не на код браузера и не на общую библиотеку.
Estus Flask
Хорошо, так что вы рекомендуете вручную определить 2 функции getClassName (статические и экземпляры), чтобы обойти черт возьми и разрешить полную минимизацию (без раздражающего RegEx). Этот пункт о библиотеке имеет большой смысл. Имеет много смысла. Для меня мой проект - это небольшое приложение Cordova, созданное самим собой, так что это не проблема. Что-нибудь кроме этого я могу видеть, что эти проблемы поднимаются. Спасибо за обсуждение! Если вы можете подумать о каких-либо улучшениях поста, не стесняйтесь редактировать его.
Джеймс Л.
1
Да, я изначально использовал nameкод DRYer для извлечения имени сущности, которая использует класс (service, plugin и т. Д.), Из имени класса, но в итоге я обнаружил, что он явно дублирует его с помощью static prop ( id, _name) это просто самый солидный подход. Хорошая альтернатива - не обращать внимания на имя класса, использовать сам класс (объект функции) в качестве идентификатора для сущности, которая отображается на этот класс и importгде это необходимо (подход, который использовался Angular 2 DI).
Настой
8

Получение имени класса непосредственно из класса

В предыдущих ответах объяснялось, что someClassInstance.constructor.nameэто прекрасно работает, но если вам нужно программно преобразовать имя класса в строку и не хотите создавать экземпляр только для этого, запомните:

typeof YourClass === "function"

И, поскольку каждая функция имеет nameсвойство, другой хороший способ получить строку с именем вашего класса - это просто сделать:

YourClass.name

Далее следует хороший пример того, почему это полезно.

Загрузка веб-компонентов

Как учит нас документация MDN , вы загружаете веб-компонент следующим образом:

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

Откуда YourComponentидет класс HTMLElement? Поскольку считается хорошей практикой присваивать классу вашего компонента после самого тега компонента, было бы неплохо написать вспомогательную функцию, которую все ваши компоненты могли бы использовать для регистрации самих себя. Итак, вот эта функция:

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

Так что все, что вам нужно сделать, это:

registerComponent(YourComponent);

Что приятно, потому что это менее подвержено ошибкам, чем сам тэг компонента. Чтобы обернуть это, это upperCamelCaseToSnakeCase()функция:

// 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());
}
Лучио Пайва
источник
Спасибо. Пример на стороне клиента. Как уже упоминалось, существуют некоторые проблемы с использованием имен функций в браузерах. Ожидается, что почти каждый фрагмент кода браузера будет минимизирован, но это испортит код, основанный на имени функции.
Настой Estus
Да, ты совершенно прав. Чтобы этот подход работал, минификатор должен быть настроен так, чтобы он не касался имен классов.
Лучио Пайва
1

Для трансплантации вавилона (до минификации)

Если вы используете Babel с @babel/preset-env, можно сохранить определения классов без преобразования их в функции (что удаляет constructorсвойство)

Вы можете отказаться от совместимости старого браузера с этой конфигурацией в babel.config / babelrc:

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

Дополнительная информация о targets: https://babeljs.io/docs/en/babel-preset-env#targets

Для минификации вавилона (после транспиляции)

Похоже, что сейчас нет простого решения ... Нам нужно взглянуть на искажения.

Если не
источник
Можете ли вы объяснить, как это должно помочь в минификации? class Foo {}будет уменьшен до чего-то, как class a{}с любой целью. Там не будет ни Fooслова в минимизированном исходном коде.
Настой Эстус
Честно говоря, я не копал больше, чем документацию и тот факт, что это помогло мне использовать эту конфигурацию ... Я использую ECSY в проекте с транспонированием babel, и мне требуется этот параметр для получения действительных имен классов: github.com/MozillaReality/ecsy/issues/ 119
Ifnot
Понимаю. Это очень специфично для кода, с которым вы работали. Например, имена могут быть сохранены для модулей ES и ES6, потому что export class Foo{}они не могут быть эффективно расширены в дальнейшем, но это может отличаться в других местах, мы не можем знать, как именно, не имея четкого представления о том, что происходит с конкретными частями кода во время сборки. В любом случае, это не изменилось с 2015 года. Это всегда было возможно для некоторых конфигураций компиляции и кода. И я бы сказал, что эта возможность все еще слишком хрупкая, чтобы использовать имена классов для логики приложения. Имя класса, на которое вы полагаетесь, может стать мусором после одного случайного изменения исходного кода
Estus Flask
1
Хорошо, я нашел то, что происходит, посмотрев на код. Мое решение исправляет перенос классов в функции. Так что это помогает до минификации. Но не с проблемой минификации. Я должен продолжать копать, потому что я не мог понять, как весь мой код по- constructor.nameпрежнему работает в минимизированной версии ... нелогично: /
Ifnot