typeof Tudo e mal-entendidos pato

imagem


Alguém usando JavaScript maravilhoso para qualquer finalidade que fosse a pergunta: por que typeof " nulo " é nulo ? typeof de uma função retorna "function" , mas from Array - "object" ? e onde getClass em suas aulas E, embora a maior parte das vezes as especificações e os fatos históricos respondam fácil e naturalmente, eu gostaria de traçar uma linha um pouco ... mais para mim.


Se, leitor, seu tipo e instância de não forem suficientes para você em suas tarefas, e você desejar alguns detalhes específicos, em vez de "objetos" , poderá ser útil posteriormente. Ah, sim, sobre os patos: eles também estarão um pouco errados.


Breve histórico


Obter um tipo confiável de variável em JavaScript sempre foi uma tarefa não trivial, certamente não para iniciantes. Na maioria dos casos, é claro que não é necessário, apenas:


if (typeof value === 'object' && value !== null) { // awesome code around the object } 

e agora você não pega Cannot read property of null - um análogo local da NPE. Isso é familiar?


E então começamos a usar funções como construtores com mais e mais frequência, e às vezes é útil inspecionar o tipo de um objeto criado dessa maneira. Mas apenas usar typeof da instância não funcionará, pois obtemos o "objeto" corretamente.


Então ainda era normal usar um protótipo de modelo OOP em JavaScript, lembra? Temos algum objeto e, através do link para seu protótipo, podemos encontrar a propriedade construtora que aponta para a função com a qual o objeto foi criado. E então um pouco de mágica com toString da função e expressões regulares, e aqui está o resultado:


 f.toString().match(/function\s+(\w+)(?=\s*\()/m)[1] 

Às vezes eles faziam essas perguntas em entrevistas, mas por quê?


Sim, podemos salvar uma representação de string do tipo como uma propriedade especial e obtê-la do objeto através da cadeia de protótipos:


 function Type() {}; Type.prototype.typeName = 'Type'; var o = new Type; o.typeName; < "Type" 

Somente duas vezes você precisa escrever "Type" : na declaração da função e na propriedade


Para os objetos internos (como Array ou Date ), tínhamos uma propriedade secreta [[Class]] , que podia ser conectada via toString a partir do Object padrão:


 Object.prototype.toString.call(new Array); < "[object Array]" 

Agora temos classes e tipos personalizados são finalmente corrigidos na linguagem: este não é um LiveScript para você; nós escrevemos código suportado em grandes quantidades!


Na mesma época, Symbol.toStringTag e Function.name apareceram , com os quais podemos levar nosso tipo de uma nova maneira.


Em geral, antes de prosseguirmos, quero observar que o problema em questão evolui no StackOverflow junto com o idioma e sobe do conselho editorial para o conselho editorial: há 9 anos , 7 anos atrás , não há muito tempo, ou isso e aquilo .


Situação atual


Anteriormente, já examinamos em detalhes suficientes Symbol.toStringTag e Function.name . Em resumo, o caractere interno toStringTag é moderno [[Class]] , apenas podemos redefini-lo para nossos objetos. E a propriedade Function.name é legalizada em quase todos os navegadores do mesmo tipo TypeName do exemplo: retorna o nome da função.


Sem hesitar, você pode definir uma função:


 function getTag(any) { if (typeof any === 'object' && any !== null) { if (typeof any[Symbol.toStringTag] === 'string') { return any[Symbol.toStringTag]; } if (typeof any.constructor === 'function' && typeof any.constructor.name === 'string') { return any.constructor.name; } } return Object.prototype.toString.call(any).match(/\[object\s(\w+)]/)[1]; } 

  1. Se a variável for um objeto, então:
    1.1 Se o objeto for substituído emStringTag , retorne-o;
    1.2 Se a função construtora for conhecida pelo objeto e a função tiver uma propriedade name , retorne-a;
  2. FINALMENTE DE OUTRA FORMA, use o método toString do objeto Object , que fará todo o trabalho polimórfico para nós para absolutamente qualquer outra variável.

Objeto com toStringTag :


 let kitten = { [Symbol.toStringTag]: 'Kitten' }; getTag(kitten); < "Kitten" 

Classe com toStringTag :


 class Cat { get [Symbol.toStringTag]() { return 'Kitten'; } } getTag(new Cat); < "Kitten" 

Usando constructor.name :


 class Dog {} getTag(new Dog); < "Dog" 

→ Mais exemplos podem ser encontrados neste repositório.


Portanto, agora é muito fácil determinar o tipo de qualquer variável em JavaScript. Essa função permite que você verifique uniformemente as variáveis ​​quanto ao tipo e use uma expressão de chave simples em vez das verificações de pato nas funções polimórficas. Em geral, eu nunca gostei da abordagem baseada na digitação de pato, eles dizem que se algo tem a propriedade de emenda , é uma matriz ou algo assim?


Alguns patos errados


É claro que entender o tipo de uma variável pela presença de certos métodos ou propriedades é da conta de todos e depende da situação. Mas vou pegar essa getTag e inspecionar algumas coisas da linguagem.


Aulas?


Meu "pato" favorito em JavaScript são as aulas. Os caras que começaram a escrever JavaScript com o ES-2015, às vezes não suspeitam do que são essas classes. E a verdade é:


 class Person { constructor(name) { this.name = name; } hello() { return this.name; } } let user = new Person('John'); user.hello(); < "John" 

Temos uma palavra-chave de classe , um construtor, alguns métodos, até se estende . Também criamos uma instância dessa classe através de new . Parece uma classe no seu sentido usual - significa uma classe!


No entanto, quando você começa a adicionar novos métodos em tempo real à "classe" e, ao mesmo tempo, eles ficam imediatamente disponíveis para instâncias já criadas, alguns são perdidos:


 Person.prototype.hello = function() { return `Is not ${this.name}`; } user.hello(); < "Is not John" 

Não faça isso!


E nem alguns sabem com segurança que isso é apenas um açúcar sintático sobre o modelo de protótipo, porque conceitualmente nada mudou na linguagem. Se chamarmos getTag de Person , obtemos "Function" , não a "Class" criada, e vale a pena lembrar.


Outras funções


Existem várias maneiras de declarar uma função no JavaScript: FunctionDeclaration , FunctionExpression e recentemente ArrowFunction . Todos sabemos quando e o que usar: as coisas são bem diferentes. Além disso, se chamarmos getTag da função declarada por qualquer uma das opções propostas, obteremos "Function" .


De fato, existem muitas outras maneiras de definir uma função em um idioma. Adicionamos à lista pelo menos a ClassDeclaration considerada e, em seguida, funções e geradores assíncronos, etc .: AsyncFunctionDeclaration , AsyncFunctionExpression , AsyncArrowFunction , GeneratorDeclaration , GeneratorExpression , ClassExpression , MethodDefinition (a lista não está completa). E parece que eles dizem e daí? todos os itens acima se comportam como uma função - o que significa que getTag também retornará "Function" . Mas há uma característica: todas as opções são certamente funções, mas nem todas diretamente - Função .


Existem subtipos internos de Função :


O construtor Function foi projetado para ser subclassável.
Não há meios sintáticos para criar instâncias das subclasses Function, exceto as subclasses GeneratorFunction e AsyncFunction internas.

Temos Function e dentro de GeneratorFunction e AsyncFunction com seus construtores "herdados" dele. Isso enfatiza que asinks e geradores têm sua própria natureza. E como resultado:


 async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } getTag(sleep); < "AsyncFunction" 

Ao mesmo tempo, não podemos instanciar tal função através do novo operador, e sua chamada retorna Promessa para nós:


 getTag(sleep(100)); < "Promise" 

Um exemplo com uma função de gerador:


 function* incg(i) { while(1) yield i += 1; } getTag(incg); < "GeneratorFunction" 

Chamar essa função retorna uma instância - o objeto Generator :


 let inc = incg(0); getTag(inc); < "Generator" 

O símbolo toStringTag é bastante redefinido para asinks e geradores. Mas typeof para qualquer função mostrará "function" .


Objetos embutidos


Temos coisas como Definir , Mapa , Data ou Erro . A aplicação de getTag a eles retornará "Function" , porque essas são as funções - construtores de coleções iteráveis, datas e erros. Das instâncias que obtemos respectivamente - "Set" , "Map" , "Date" e " Error ".


Mas cuidado! ainda existem objetos como JSON ou Math . Se você se apressar, poderá assumir uma situação semelhante. Mas não! isso é completamente diferente - objetos únicos embutidos. Eles não são instanciados ( is not a constructor ). Uma chamada typeof retornará "object" (quem duvidaria). Mas o getTag passará para toStringTag e obterá "JSON" e "Math" . Esta é a última observação que quero compartilhar.


Vamos sem fanatismo


Recentemente, perguntei um pouco mais sobre o tipo de variável em JavaScript quando decidi escrever meu inspetor de objetos simples para análise dinâmica de código (brincadeira). O material não é publicado apenas na programação Anormal , já que você não precisa dele na produção: existe o tipo de , instanceof , Array.isArray , isNaN e tudo o mais que vale a pena lembrar ao realizar a verificação necessária. A maioria dos projetos possui TypeScript, Dart ou Flow. Eu simplesmente amo JavaScript !

Source: https://habr.com/ru/post/pt427253/


All Articles