O operador instanceof
permite verificar se um objeto pertence a uma determinada classe. Também leva em consideração a herança.
Esta verificação pode ser necessária em muitos casos. Por exemplo, pode ser usado para construir uma função polimórfica , aquela que trata os argumentos de forma diferente dependendo do seu tipo.
A sintaxe é:
obj instância da classe
Ele retorna true
se obj
pertence à Class
ou a uma classe herdada dela.
Por exemplo:
classe Coelho {} deixe coelho = new Coelho(); // é um objeto da classe Rabbit? alerta (instância de coelho de Coelho); // verdadeiro
Também funciona com funções construtoras:
//em vez da classe função Coelho() {} alert(new Rabbit() instância de Rabbit); // verdadeiro
…E com classes integradas como Array
:
deixe arr = [1, 2, 3]; alerta (arr instância de Array); // verdadeiro alert(arr instância do objeto); // verdadeiro
Observe que arr
também pertence à classe Object
. Isso ocorre porque Array
herda prototipicamente de Object
.
Normalmente, instanceof
examina a cadeia de protótipos para a verificação. Também podemos definir uma lógica personalizada no método estático Symbol.hasInstance
.
O algoritmo de obj instanceof Class
funciona aproximadamente da seguinte maneira:
Se houver um método estático Symbol.hasInstance
, basta chamá-lo: Class[Symbol.hasInstance](obj)
. Deve retornar true
ou false
e pronto. É assim que podemos personalizar o comportamento de instanceof
.
Por exemplo:
// configura instanceOf verificação que assume que // qualquer coisa com propriedade canEat é um animal classe Animal { estático [Symbol.hasInstance](obj) { if (obj.canEat) retornar verdadeiro; } } deixe obj = {canEat: true}; alerta(obj instância de Animal); // verdadeiro: Animal[Symbol.hasInstance](obj) é chamado
A maioria das classes não possui Symbol.hasInstance
. Nesse caso, a lógica padrão é usada: obj instanceOf Class
verifica se Class.prototype
é igual a um dos protótipos na cadeia de protótipos obj
.
Em outras palavras, compare um após o outro:
obj.__proto__ === Classe.protótipo? obj.__proto__.__proto__ === Classe.protótipo? obj.__proto__.__proto__.__proto__ === Classe.protótipo? ... // se alguma resposta for verdadeira, retorna verdadeiro // caso contrário, se chegarmos ao final da cadeia, retorne false
No exemplo acima rabbit.__proto__ === Rabbit.prototype
, isso dá a resposta imediatamente.
No caso de herança, a partida será na segunda etapa:
classe Animal {} classe Coelho estende Animal {} deixe coelho = new Coelho(); alerta(instância de coelho de Animal); // verdadeiro // coelho.__proto__ === Animal.prototype (sem correspondência) // coelho.__proto__.__proto__ === Animal.prototype (correspondência!)
Aqui está a ilustração do que rabbit instanceof Animal
compara com Animal.prototype
:
A propósito, há também um método objA.isPrototypeOf(objB), que retorna true
se objA
estiver em algum lugar na cadeia de protótipos de objB
. Portanto, o teste de obj instanceof Class
pode ser reformulado como Class.prototype.isPrototypeOf(obj)
.
É engraçado, mas o próprio construtor Class
não participa da verificação! Apenas a cadeia de protótipos e Class.prototype
é importante.
Isso pode levar a consequências interessantes quando uma propriedade prototype
é alterada após a criação do objeto.
Como aqui:
função Coelho() {} deixe coelho = new Coelho(); //alterou o protótipo Coelho.prototype = {}; // ...não é mais um coelho! alerta (instância de coelho de Coelho); // falso
Já sabemos que objetos simples são convertidos em string como [object Object]
:
deixe obj = {}; alerta(obj); // [objeto Objeto] alerta(obj.toString()); // o mesmo
Essa é a implementação deles toString
. Mas há um recurso oculto que torna toString
muito mais poderoso que isso. Podemos usá-lo como um typeof
estendido e uma alternativa para instanceof
.
Parece estranho? De fato. Vamos desmistificar.
Por especificação, o toString
integrado pode ser extraído do objeto e executado no contexto de qualquer outro valor. E seu resultado depende desse valor.
Para um número, será [object Number]
Para um booleano, será [object Boolean]
Para null
: [object Null]
Para undefined
: [object Undefined]
Para matrizes: [object Array]
…etc (personalizável).
Vamos demonstrar:
//copia o método toString em uma variável por conveniência deixe objectToString = Object.prototype.toString; // que tipo é esse? deixe arr = []; alerta(objetoToString.call(arr)); // [matriz de objetos]
Aqui usamos call conforme descrito no capítulo Decoradores e encaminhamento, call/apply para executar a função objectToString
no contexto this=arr
.
Internamente, o algoritmo toString
examina this
e retorna o resultado correspondente. Mais exemplos:
deixe s = Object.prototype.toString; alerta( s.call(123) ); // [número do objeto] alerta(s.call(nulo)); // [objeto Nulo] alerta( s.call(alerta) ); // [função do objeto]
O comportamento do Object toString
pode ser personalizado usando uma propriedade especial do objeto Symbol.toStringTag
.
Por exemplo:
deixe usuário = { [Symbol.toStringTag]: "Usuário" }; alerta({}.toString.call(usuário)); // [objeto Usuário]
Para a maioria dos objetos específicos do ambiente, existe essa propriedade. Aqui estão alguns exemplos específicos de navegador:
// toStringTag para o objeto e a classe específicos do ambiente: alerta(janela[Symbol.toStringTag]); // Janela alerta(XMLHttpRequest.prototype[Symbol.toStringTag]); //XMLHttpRequest alerta({}.toString.call(janela)); // [janela do objeto] alerta({}.toString.call(novo XMLHttpRequest()) ); // [objeto XMLHttpRequest]
Como você pode ver, o resultado é exatamente Symbol.toStringTag
(se existir), agrupado em [object ...]
.
No final temos “typeof on steroids” que não funciona apenas para tipos de dados primitivos, mas também para objetos integrados e até pode ser customizado.
Podemos usar {}.toString.call
em vez de instanceof
para objetos integrados quando quisermos obter o tipo como uma string em vez de apenas verificar.
Vamos resumir os métodos de verificação de tipo que conhecemos:
trabalha para | retorna | |
---|---|---|
typeof | primitivos | corda |
{}.toString | primitivas, objetos integrados, objetos com Symbol.toStringTag | corda |
instanceof | objetos | verdadeiro/falso |
Como podemos ver, {}.toString
é tecnicamente um typeof
“mais avançado”.
E o operador instanceof
realmente brilha quando estamos trabalhando com uma hierarquia de classes e queremos verificar a classe levando em consideração a herança.
importância: 5
No código abaixo, por que instanceof
retorna true
? Podemos ver facilmente que a
não é criado por B()
.
função A() {} função B() {} A.protótipo = B.protótipo = {}; deixe a = new A(); alert(uma instância de B); // verdadeiro
Sim, parece realmente estranho.
Mas instanceof
não se preocupa com a função, mas sim com seu prototype
, que corresponde à cadeia de protótipos.
E aqui a.__proto__ == B.prototype
, então instanceof
retorna true
.
Portanto, pela lógica de instanceof
, o prototype
na verdade define o tipo, não a função construtora.