Obter o nome da classe da instância da classe ES6

157

Existem maneiras 'harmoniosas' de obter o nome da classe da instância da classe ES6? Outro que não seja

someClassInstance.constructor.name

Atualmente, estou contando com a implementação do Traceur. E parece que Babel tem um polyfill por Function.nameenquanto Traceur não.

Para resumir tudo: não havia outro caminho no ES6 / ES2015 / Harmony, e nada é esperado no caixa eletrônico no ES.Next.

Ele pode fornecer padrões úteis para aplicativos do servidor não minificados, mas é indesejável em aplicativos destinados ao navegador / desktop / celular.

Babel usacore-js para polyfill Function.name; ele deve ser carregado manualmente para aplicativos Traceur e TypeScript conforme apropriado.

Estus Flask
fonte
2
Me deparei com o mesmo problema; para Traceur, a única solução era analisar o próprio código de classe para extrair o nome, o que não acho qualificado como harmonioso. Eu apenas engoli a pílula e mudei para Babel; O desenvolvimento do Traceur parece estar um pouco estagnado, e muitos recursos do ES6 são mal implementados. Como você mencionou, instance.constructor.namee class.nameretorne o nome da classe no ES6 adequado.
Andrew Odri 27/03/2015
Parece ser o único caminho.
Frederik Krautwald
Isso está no padrão ES6?
Drudru
12
Vale ressaltar que isso someClassInstance.constructor.nameserá confundido se você atualizar seu código.
21440
stackoverflow.com/questions/2648293/… Pode querer olhar para isso, deve funcionar com .constructor.
Florrie

Respostas:

206

someClassInstance.constructor.nameé exatamente a maneira correta de fazer isso. Os transpilers podem não suportar isso, mas é o caminho padrão conforme a especificação. (A namepropriedade das funções declaradas por meio das produções ClassDeclaration é definida em 14.5.15 , etapa 6.)

Domenic
fonte
2
Era disso que eu tinha medo. Você conhece algum polyfill razoável para isso? Eu tentei descobrir como Babel faz isso, mas teve muito pouco sucesso.
Estus Flask
Eu realmente não sei o que você quer dizer com um polyfill para um recurso de idioma (classes).
Domenic
Quero dizer o polyfill para constructor.name. Parece que Babel o implementou, mas não consegui entender como isso acontece.
Estus Flask
2
@estus someClassInstance.constructoré uma função. Todas as funções têm uma namepropriedade definida como seu nome. É por isso que babel não precisa fazer nada. Por favor, veja developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/…
Esteban
2
@Esteban Parece que o Babel promove persistentemente polyfills core-js (incluindo um polyfill para Function.name), é por isso que algumas compilações do Babel podem funcionar prontamente em todos os navegadores.
Estus Flask
53

Como o @Domenic diz, use someClassInstance.constructor.name. @Esteban menciona nos comentários que

someClassInstance.constructoré uma função. Todas as funções têm uma namepropriedade ...

Portanto, para acessar o nome da classe estaticamente, faça o seguinte (isso funciona com minha versão Babel BTW. De acordo com os comentários em @Domenic, sua milhagem pode variar).

class SomeClass {
  constructor() {}
}

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

Atualizar

Babel estava bem, mas uglify / minification acabou me causando problemas. Estou criando um jogo e criando um hash de recursos de Sprite em pool (onde a chave é o nome da função). Após a minificação, todas as funções / classes foram nomeadas t. Isso mata o hash. Estou usando Gulpneste projeto e, depois de ler os documentos do gulp-uglify , descobri que há um parâmetro para impedir que essa variável / nome da função local seja alterada . Então, no meu gulpfile eu mudei

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

Há uma troca de desempenho versus legibilidade aqui. Não manipular os nomes resultará em um arquivo de compilação (um pouco) maior (mais recursos de rede) e execução de código potencialmente mais lenta (citação necessária - pode ser BS). Por outro lado, se eu mantivesse o mesmo, teria que definir manualmente getClassNameem todas as classes ES6 - no nível estático e de instância. Não, obrigado!

Atualizar

Após a discussão nos comentários, parece que evitar a .nameconvenção em favor da definição dessas funções é um bom paradigma. Leva apenas algumas linhas de código e permitirá minificação e generalidade completas do seu código (se usado em uma biblioteca). Acho que mudo de idéia e defino manualmente getClassNamenas minhas aulas. Obrigado @estus! . Getter / Setters geralmente são uma boa idéia em comparação com o acesso direto a variáveis ​​de qualquer maneira, especialmente em um aplicativo baseado em cliente.

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.
fonte
3
Desabilitar completamente o mangling não é uma boa idéia, porque o mangling contribui muito para a minificação. Não é uma idéia muito boa usar o assunto no código do lado do cliente, mas as classes podem ser protegidas contra reservedmanipulação com a opção Uglify (uma lista de classes pode ser obtida com regexp ou o que for).
Estus Flask
Muito verdadeiro. Há uma troca de ter um tamanho de arquivo maior. Parece que é assim que você pode usar o RegEx para evitar a dispersão apenas de itens selecionados . O que você quer dizer com "não é uma boa idéia usar o assunto no código do lado do cliente"? Isso representaria um risco de segurança em alguns cenários?
James L.
1
Não, exatamente o que já foi dito. É normal que o JS do lado do cliente seja reduzido, portanto, já se sabe que esse padrão causará problemas para o aplicativo. Uma linha de código extra para o identificador de sequência de classes em vez de um namepadrão puro pode ser apenas uma vantagem para todos. O mesmo pode ser aplicado ao Nó, mas em menor grau (por exemplo, aplicativo Electron ofuscado). Como regra geral, eu confiaria no namecódigo do servidor, mas não no código do navegador e na biblioteca comum.
Estus Flask
OK, então você está recomendando definir manualmente 2 funções getClassName (estática e instância) para contornar o inferno e permitir a minificação completa (sem um RegEx irritante). Esse ponto sobre a biblioteca faz muito sentido. Faz muito sentido. Para mim, meu projeto é um pequeno aplicativo Cordova feito por você, portanto, esses não são realmente problemas. Qualquer coisa além disso posso ver esses problemas surgindo. Obrigado pela discussão! Se você puder pensar em alguma melhoria na postagem, fique à vontade para editá-la.
James L.
1
Sim, inicialmente usei o namecódigo DRYer para extrair o nome da entidade que usa a classe (serviço, plug-in, etc.) do nome da classe, mas no final, descobri que a duplicava explicitamente com prop estático ( id,_name ) é apenas a abordagem mais sólida. Uma boa alternativa é não se importar com o nome da classe, usar a própria classe (objeto de função) como um identificador para uma entidade que é mapeada para essa classe e importsempre que necessário (uma abordagem usada pelo Angular 2 DI).
Estus Flask
8

Obtendo o nome da turma diretamente da turma

As respostas anteriores explicaram que someClassInstance.constructor.namefunciona muito bem, mas se você precisar converter programaticamente o nome da classe em uma string e não desejar criar uma instância apenas para isso, lembre-se:

typeof YourClass === "function"

E, como toda função tem uma namepropriedade, outra maneira agradável de obter uma string com o nome da sua classe é:

YourClass.name

O que se segue é um bom exemplo de por que isso é útil.

Carregando componentes da Web

Como a documentação do MDN nos ensina, é assim que você carrega um componente da web:

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

Onde YourComponent uma classe se estende HTMLElement. Como é uma boa prática nomear a classe do seu componente após a própria tag do componente, seria bom escrever uma função auxiliar que todos os seus componentes pudessem usar para se registrar. Então aqui está essa função:

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

Então, tudo que você precisa fazer é:

registerComponent(YourComponent);

O que é legal porque é menos propenso a erros do que você mesmo escrever a tag do componente. Para finalizar, esta é a upperCamelCaseToSnakeCase()função:

// 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
fonte
Obrigado. O exemplo é do lado do cliente. Como já foi mencionado, existem alguns problemas com o uso de nomes de funções nos navegadores. Espera-se que quase todo código do navegador seja minificado, mas isso arruinará o código que depende do nome da função.
Estus Flask
Sim, você está totalmente certo. O minificador teria que ser configurado para não tocar nos nomes das classes para que essa abordagem funcionasse.
Lucio Paiva
1

Para transpilação de babel (antes da minificação)

Se você estiver usando o Babel @babel/preset-env, é possível manter as definições de classes sem convertê-las em funções (o que remove a constructorpropriedade)

Você pode descartar alguma compatibilidade antiga do navegador com esta configuração em babel.config / babelrc:

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

Mais informações sobre targets: https://babeljs.io/docs/en/babel-preset-env#targets

Para minificação de babel (após transpilação)

Parece que não há uma solução fácil no momento ... Precisamos examinar exclusões desconcertantes.

Se não
fonte
Você pode explicar melhor como isso deve ajudar na minificação? class Foo {}será reduzido para algo como class a{}com qualquer alvo. Não haverá Foopalavra no código fonte minificado.
Estus Flask
Honestamente, não cavei mais do que as documentações e o fato de me ajudar a usar essa configuração ... Estou usando o ECSY no projeto transpiled babel e exige esse parâmetro para obter nomes de classe válidos: github.com/MozillaReality/ecsy/issues/ 119
Ifnot
Entendo. Isso é muito específico para o código com o qual você lidou. Por exemplo, nomes podem ser preservados para os módulos ES e ES6 porque export class Foo{}não podem ser melhorados com eficiência, mas isso pode ser diferente em outros lugares; não podemos saber exatamente exatamente sem ter uma boa idéia do que acontece com partes de código específicas no momento da construção. De qualquer forma, isso não mudou desde 2015. Sempre foi possível para algumas configurações e códigos de compilação. E eu diria que essa possibilidade ainda é muito frágil para usar nomes de classe para a lógica do aplicativo. O nome da classe em que você confia pode se tornar lixo após uma alteração acidental no código-fonte
Estus Flask
1
Ok, eu encontrei o que está acontecendo, olhando para o código. Minha solução corrige a transpilação de classes para funções. Por isso, ajuda antes da minificação. Mas não com o problema de minificação. Eu tenho que continuar cavando porque, eu não conseguia entender como todo o meu código usando constructor.nameainda funciona na versão minificada ... ilógico: /
Caso contrário