El operador instanceof
de permite comprobar si un objeto pertenece a una determinada clase. También tiene en cuenta la herencia.
Esta verificación puede ser necesaria en muchos casos. Por ejemplo, se puede utilizar para construir una función polimórfica , aquella que trata los argumentos de manera diferente según su tipo.
La sintaxis es:
obj instancia de clase
Devuelve true
si obj
pertenece a la Class
o a una clase que hereda de ella.
Por ejemplo:
clase Conejo {} let conejo = nuevo Conejo(); // ¿Es un objeto de la clase Rabbit? alerta (instancia de conejo de Conejo); // verdadero
También funciona con funciones constructoras:
// en lugar de clase función Conejo() {} alerta (nueva instancia de Rabbit() de Rabbit); // verdadero
…Y con clases integradas como Array
:
sea arr = [1, 2, 3]; alerta (arr instancia de matriz); // verdadero alerta (arr instancia del objeto); // verdadero
Tenga en cuenta que arr
también pertenece a la clase Object
. Esto se debe a que Array
hereda prototípicamente de Object
.
Normalmente, instanceof
de examina la cadena prototipo para la verificación. También podemos configurar una lógica personalizada en el método estático Symbol.hasInstance
.
El algoritmo de obj instanceof Class
funciona aproximadamente de la siguiente manera:
Si hay un método estático Symbol.hasInstance
, simplemente llámelo: Class[Symbol.hasInstance](obj)
. Debería devolver true
o false
y listo. Así es como podemos personalizar el comportamiento de instanceof
.
Por ejemplo:
// configura la verificación de instancia de que asume que // cualquier cosa con la propiedad canEat es un animal clase Animal { estático [Símbolo.hasInstance](obj) { si (obj.canEat) devuelve verdadero; } } let obj = {canEat: verdadero}; alerta (obj instancia de Animal); // verdadero: se llama a Animal[Symbol.hasInstance](obj)
La mayoría de las clases no tienen Symbol.hasInstance
. En ese caso, se utiliza la lógica estándar: obj instanceOf Class
comprueba si Class.prototype
es igual a uno de los prototipos en la cadena de prototipos obj
.
En otras palabras, compare uno tras otro:
obj.__proto__ === Clase.prototipo? obj.__proto__.__proto__ === Clase.prototipo? obj.__proto__.__proto__.__proto__ === Clase.prototipo? ... // si alguna respuesta es verdadera, devuelve verdadero // en caso contrario, si llegamos al final de la cadena, devolvemos falso
En el ejemplo anterior rabbit.__proto__ === Rabbit.prototype
, eso da la respuesta de inmediato.
En el caso de herencia, la coincidencia se hará en el segundo paso:
clase Animal {} clase Conejo extiende Animal {} let conejo = nuevo Conejo(); alerta (instancia de conejo de Animal); // verdadero // conejo.__proto__ === Animal.prototype (no coincide) // conejo.__proto__.__proto__ === Animal.prototipo (¡coincide!)
Aquí está la ilustración de lo que rabbit instanceof Animal
se compara con Animal.prototype
:
Por cierto, también hay un método objA.isPrototypeOf(objB), que devuelve true
si objA
está en algún lugar de la cadena de prototipos de objB
. Entonces, la prueba de obj instanceof Class
se puede reformular como Class.prototype.isPrototypeOf(obj)
.
¡Es curioso, pero el constructor Class
en sí no participa en la verificación! Sólo importa la cadena de prototipos y Class.prototype
.
Esto puede tener consecuencias interesantes cuando se cambia una propiedad prototype
después de crear el objeto.
Como aquí:
función Conejo() {} let conejo = nuevo Conejo(); //cambió el prototipo Conejo.prototipo = {}; // ... ¡ya no soy un conejo! alerta (instancia de conejo de Conejo); // FALSO
Ya sabemos que los objetos simples se convierten en cadenas como [object Object]
:
dejar objeto = {}; alerta(obj); // [objeto Objeto] alerta(obj.toString()); // lo mismo
Esa es su implementación de toString
. Pero hay una característica oculta que hace que toString
sea mucho más poderoso que eso. Podemos usarlo como un typeof
extendido y una alternativa para instanceof
.
¿Suena extraño? En efecto. Desmitifiquemos.
Por especificación, el toString
integrado se puede extraer del objeto y ejecutar en el contexto de cualquier otro valor. Y su resultado depende de ese valor.
Para un número, será [object Number]
Para un booleano, será [object Boolean]
Para null
: [object Null]
Para undefined
: [object Undefined]
Para matrices: [object Array]
…etc (personalizable).
Demostremos:
// copia el método toString en una variable para mayor comodidad dejar objectToString = Object.prototype.toString; // ¿Qué tipo es este? let arr = []; alerta( objectToString.call(arr) ); // [matriz de objetos]
Aquí usamos call como se describe en el capítulo Decoradores y reenvío, call/apply para ejecutar la función objectToString
en el contexto this=arr
.
Internamente, el algoritmo toString
examina this
y devuelve el resultado correspondiente. Más ejemplos:
let s = Object.prototype.toString; alerta( s.call(123) ); // [Número de objeto] alerta( s.call(nulo) ); // [objeto nulo] alerta( s.call(alerta) ); // [función de objeto]
El comportamiento de Object toString
se puede personalizar utilizando una propiedad de objeto especial Symbol.toStringTag
.
Por ejemplo:
dejar usuario = { [Symbol.toStringTag]: "Usuario" }; alerta ({}.toString.call(usuario)); // [objeto Usuario]
Para la mayoría de los objetos específicos del entorno, existe dicha propiedad. A continuación se muestran algunos ejemplos específicos del navegador:
// toStringTag para el objeto y la clase específicos del entorno: alerta (ventana [Símbolo.toStringTag]); // Ventana alerta (XMLHttpRequest.prototype[Symbol.toStringTag]); // XMLHttpRequest alerta ({}.toString.call (ventana)); // [ventana de objeto] alerta ({}.toString.call (nuevo XMLHttpRequest())); // [objeto XMLHttpRequest]
Como puede ver, el resultado es exactamente Symbol.toStringTag
(si existe), envuelto en [object ...]
.
Al final tenemos "tipo de esteroides" que no sólo funciona para tipos de datos primitivos, sino también para objetos integrados e incluso se puede personalizar.
Podemos usar {}.toString.call
en lugar de instanceof
para objetos integrados cuando queremos obtener el tipo como una cadena en lugar de simplemente verificarlo.
Resumamos los métodos de verificación de tipos que conocemos:
trabaja para | regresa | |
---|---|---|
typeof | primitivos | cadena |
{}.toString | primitivos, objetos integrados, objetos con Symbol.toStringTag | cadena |
instanceof | objetos | verdadero/falso |
Como podemos ver, {}.toString
es técnicamente un typeof
"más avanzado".
Y el operador instanceof
realmente brilla cuando trabajamos con una jerarquía de clases y queremos verificar la clase teniendo en cuenta la herencia.
importancia: 5
En el siguiente código, ¿por qué instanceof
devuelve true
? Podemos ver fácilmente que a
no es creado por B()
.
función A() {} función B() {} A.prototipo = B.prototipo = {}; sea a = nueva A(); alerta (una instancia de B); // verdadero
Sí, parece realmente extraño.
Pero instanceof
no le importa la función, sino su prototype
, que compara con la cadena de prototipos.
Y aquí a.__proto__ == B.prototype
, por lo que instanceof
devuelve true
.
Entonces, según la lógica de instanceof
, el prototype
en realidad define el tipo, no la función constructora.