¿De que tipo es mi variable? – typeof e instance of en Javascript

Algunas veces cuando estás programando necesitas validar el tipo de dato con el que estás trabajando.

Puede ser que al llamar un API, te regrese un objeto o un arreglo y tienes que saber si debes acceder directamente a la propiedad de la respuesta o iterar sobre los elementos.

Puede ser que aceptes solo números decimales pero por alguna razón te puedan llegar tanto en formato String como Number. Entonces tienes que validar si te llega como un String parseable a Number antes de hacer las operaciones ya que si te envían letras o simbolos el programa tendría un error.

Posibilidades hay muchas y por ende es importante que tengas las herramientas para poder averiguar que es lo que tienen tus variables.

Supón que tienes estas varibles (y función) en tu código:


const a = 10
const b = 34.5
const c = 'Justice League'
const d = true
const e = {}
const f = {
    g1: 'The child'
}
const g = []
const h = [1,2,3]
const i = new m('TheName')
const j = undefined
const k = null
const l = Symbol('Sim')

function m (name) {
    this.name = name
}

¿Sabes de memoria que te puede regresar Javascript al llamar a typeof con cada uno?

typeof

Para utilizar typeof simplemente necesitamos colocarlo como prefijo al valor a evaluar:

console.log(typeof "Hello") // string

Para saber que tipo de dato guarda cada variable solo tenemos que replicar esta llamada para las variables anteriores. Aquí te las agrego por si te da flojera escribirlas:

console.log(typeof a)
console.log(typeof b)
console.log(typeof c)
console.log(typeof d)
console.log(typeof e)
console.log(typeof f)
console.log(typeof g)
console.log(typeof h)
console.log(typeof i)
console.log(typeof j)
console.log(typeof k)
console.log(typeof l)
console.log(typeof m)

Ahora analicemos cada resultado.

*Para los siguientes ejemplos no incluiré la llamada a console.log() ya que solo sería agregar ruido. En su lugar pondré las variables con su valor para que sea mas claro ver de lo que estamos hablando.

Las primeras 4 variables son bastante intuitivas y regresan tipos de datos que probablemente ya te imaginas:

const a = 10  // number
const b = 34.5  // number
const c = 'Justice League'  // string
const d = true  // boolean

Nada raro hasta el momento.

e y f son los ejemplos mas claros de lo que es un objeto en JS. Es irrelevante si el objeto tiene propiedades o si esta vacío.

const e = {}  // object
const f = {  // object
    g1: 'The child'
}

Por otra parte g y h por su parte son arreglos. ¿Así que regresarían array o algo así?

const g = []  // object
const h = [1,2,3]  // object

No realmente. Debes saber que en JS todo es un objeto y los arreglos aunque se consideran un tipo especial de objeto al final son… un objeto.

Por último, en la variable i tenemos un ejemplo de un objeto creado por una llamada a una función constructor, (la cual es la función m).

const i = new m('TheName')  // object

Ok, hasta ahora lo único poco intuitivo es el tipo que se regresa de un arreglo.

Vamos con los últimos 4 casos.

En la variable j tenemos undefined. Entonces lo lógico es que obtengamos… ¿undefined?

console.log(typeof j) // undefined

¡undefined! ¡Perfecto!

Y si eso pasa con undefined lo lógico es que pase lo mismo con null en la variable k. ¿Cierto?

const k = null  // object

Cierto. Eso sería lo lógico.

¿Pero qué paso con esto? ¿Acaso null es un tipo especial de objeto como lo son los arreglos?

La respuesta es…triste.

El que null regrese que es un tipo de dato object con typeof es un bug que viene desde hace eras. Así que en realidad no nos queda mas que lidiar con ello.

*El lado bueno es que si alguna vez te sientes mal de que tu código tenga bugs, sólo tienes que recordar que incluso un lenguaje usado por todo el mundo como Javascript tiene bugs.

La siguiente variable es una que no solemos usar mucho en desarrollo. Es un tipo de dato que se introdujo en la especificación de ES6 (la versión de JS):

const l = Symbol('Sim')  // symbol

Por último veamos la función:

function m (name) {  
    this.name = name
} // function

Ok, una función es tipo function. Eso tiene sentido. Sin embargo debo reiterar que todo en JS es un objeto. Así que técnicamente una función también es un objeto.

¿Porqué entonces una función si regresa function y un arreglo no regresa array o algo así?

Sólo dios y los desarrolladores de las primeras versiones de JS tienen esa respuesta.

instanceof

instanceof, por su parte puede ser algo confuso, ya que su funcionamiento es mucho más complejo que el de typeof. Aunque por el nombre ya puedes empezar a intuir que sirve para revisar si algo es una instancia de otra cosa.

El concepto de instancias normalmente se utiliza en lenguajes orientados a objetos. Esto es porque en estos lenguajes uno suele definir un template (al que se le llama clase) el cual se usa como guía para crear los objetos.

JS no es uno de estos lenguajes. Y a pesar de que puede comportarse de manera similar, sus bases son diferentes y por ende instanceof no es tan simple como parece a primera vista.

En pocas palabras instanceof regresa un booleano que indica si un objeto se encuentra dentro de la Prototype Chain de otro.

*Como la Prototype Chain es prácticamente la forma en la que se aplica la herencia en JS, esto tiene el mismo efecto que la herencia clásica y el polimorfismo.

Si no estás seguro de que es esto de la Prototype Chain o de como difiere la herencia en Javascript versus otros lenguajes te recomiendo que leas: ¿Qué es el prototype en JS?

Veamos un ejemplo:

function Company(companyName, ceo) {
    this.companyName = companyName
    this.ceo = ceo
}

function Artist(artistName, birthYear) {
    this.artistName = artistName
    this.birthYear = birthYear
}

const vincent = new Artist('Vincent van Gogh', 1853)
const frank = new Artist('Frank Sinatra', 1915)
const costco = new Company('Apple', 'Tim Cook')

const isVincentAnArtist = vincent instanceof Artist // true
const isFrankAnArtist = frank instanceof Artist // true
const isCostcoAnArtist = costco instanceof Artist // false

¿Sencillo, no?

Los objetos creados con Artist son instancias de Artist. Los que fueron creados con otra función, como costco, no lo son.

Las funciones Artist y Company son lo que se conoce como función constructor. Este término puede llegar a ser confuso porque también existen las funciones constructor() dentro de una clase. Y aunque son conceptos diferente hacen lo mismo en la práctica.

Para comprobarlo puedes intentar correr el mismo código pero utilizando la sintaxis de clases:

class Company {
    constructor (companyName, ceo) {
        this.companyName = companyName
        this.ceo = ceo
    }
}

class Artist {
    constructor (artistName, birthYear) {
        this.artistName = artistName
        this.birthYear = birthYear
    }
}

const vincent = new Artist('Vincent van Gogh', 1853)
const frank = new Artist('Frank Sinatra', 1915)
const costco = new Company('Apple', 'Tim Cook')

const isVincentAnArtist = vincent instanceof Artist // true
const isFrankAnArtist = frank instanceof Artist // true
const isCostcoAnArtist = costco instanceof Artist // false

La verdad es que no es tan grave confundirse entre estos 2 conceptos, pero siempre es bueno tener en cuenta que existen. Pero claro, si quieres saber la diferencia entre ellos te recomiendo el siguiente post: Javascript – la verdad detras de las clases y las funciones constructor.

Continuando con el tema…

Al llamar a la función junto con la keyword new, le estamos indicando a Javascript que construya un nuevo objeto. Dentro de este proceso es cuando se agrega a la función Artist como parte de la Prototype Chain del objeto nuevo.

instanceof por lo tanto no es usado comúnmente para valores primitivos. Su enfoque es para validar si un objeto es de cierto tipo o no.

¿Funciona con casos de herencia?

Claro que sí. Veamos un ejemplo con ambas formas.

Con funciones constructor:

function Artist(artistName, birthYear) {
    this.artistName = artistName
    this.birthYear = birthYear
}

function Singer(artistName, birthYear, albums) {
    Artist.apply(this, [artistName, birthYear])
    this.albums = albums
}

Singer.prototype = Object.create(Artist.prototype)

Con clases:

class Artist {
    constructor (artistName, birthYear) {
        this.artistName = artistName
        this.birthYear = birthYear
    }
}

class Singer extends Artist {
    constructor (artistName, birthYear, albums) {
        super(artistName, birthYear)
        this.albums = albums
    }
}

Elije la que mas te guste, el resultado al final es el mismo:

const vincent = new Artist('Vincent van Gogh', 1853)
const frank = new Singer('Frank Sinatra', 1915, ['Duets', 'L.A. Is My Lady', 'Watertown', 'A Man Alone'])

const isVincentAnArtist = vincent instanceof Artist // true
const isFrankAnArtist = frank instanceof Artist // true

const isVincentASinger = vincent instanceof Singer // false
const isFrankASinger = frank instanceof Singer // true

instanceof y typeof son las herramientas principales a la hora de validar los datos con lo que estás trabajando. Pero debido a las particularidades de JS tienen varios casos que no siempre son tan intuitivos, así que repásalos y tenlos en cuenta para que no te agarren con la guardia baja.