Escuela Frontend
JavaScript

Aprendamos Sobre los Tipos de Datos Primitivos en JavaScript

Horacio Herrera
Autor
Horacio Herrera

Si alguna vez has escuchado la frase "en JavaScript todo son objetos", te engañaron. En JavaScript tenemos por ahora siete (7) tipos de datos primitivos los cuales son importantes de aprender y saber cómo utilizar para evitar errores inesperados en nuestros programas y principalmente, sacarle provecho a sus utilidades.

¿Por qué debo aprender de Tipos de Datos Primitivos?

Los tipos de datos primitivos son una parte fundamental del lenguaje de JavaScript. No conocerlos o no saber como utilizarlos es como decir "Tengo que consumir comida saludable" sin saber que alimentos son saludables para ti y tu organismo. Saber qué son, cuales son, y la diferencia entre ellos te va a ayudar a crear un conocimiento fundamental muy importante para tu futura carrera como desarrollador.

Otra razón por la cual es beneficioso aprender sobre tipos de datos primitivos es por la naturaleza dinámica de JavaScript con respecto al tipado o lo que le llamamos coerción. Ver la coerción como algo a lo que puedes sacarle provecho y no como una debilidad es importante!

¿Qué son los Tipos de Datos Primitivos?

Los tipos de datos primitivos en JavaScript son aquellos que no poseen métodos ni propiedades. Además, los valores asignados con estos tipos de datos son inmutables, lo que quiere decir que después de asignar una variable a un valor primitivo, si deseas cambiar su valor necesitaras reasignarle un valor nuevo, ya que su valor inicial no puede ser modificado, simplemente se substituye con el nuevo valor.

Como mensioné anteriormente, tenemos siete (7) tipos primitivos en JavaScript por el momento: string, number, boolean, null, undefined, Symbol y bigint.

JavaScript nos permite saber el tipo de una variable o valor con el método typeof. Éste método siempre te devolverá string con el nombre del tipo de valor que le hayas pasado:

typeof "hola!" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof null // "object" ???
typeof undefined // "undefined"
typeof Symbol // "symbol"
typeof 23n // "bigint"

Sí, ya se que null no devuelve "lo que debería devolver" ("null"). Esto se debe a un bug histórico(*) en el lenguaje JavaSript que no se puede resolver y lastimosamente tenemos que vivir con ello.

Quizás también te preguntes: Si los tipos de datos primitivos no tienen métodos ni propiedades, como es posible que pueda hacer 'holaEscuelaFrontend'.length() y el motor de JavaScript no se queje? No te preocupes, ya hablaremos de esta peculiaridad.

Por ahora empecemos a definir cada tipo de dato:

string

Te sirve para describir cualquier cadena de texto. Tienes tres diferentes maneras de representarlo:

  • con commillas simples (')
  • con comillas dobles (")
  • con "backticks" (`)
const nombre = "Horacio"
const nombre = "Horacio"
const nombre = `Horacio`

Debes tener en cuenta que a pesar de poder representar textos de tres maneras diferentes, no debes mezclarlos para describir la misma cadena de texto.

var nombre = "Horacio' // 🚫
var nombre = "Horacio" // ✅

Una ventaja o propiedad de usar los "backticks" es que puedes interpolar cualquier expresión de JavaScript, ya sean otras variables o operaciones. Para ello, necesitas encapsular la expresión dentro de corchetes precedidos por el signo $ (${<EXPRESION_AQUÍ>})

const persona = {
nombre: "Horacio",
edad: 33,
}
const saludo = `Hola ${persona.nombre}`
const mayorDeEdad = `es ${persona.edad > 18 ? "mayor" : "menor"} de edad`

Más adelante veremos por qué podemos llamar funciones a partir de cadenas de texto, por ejemplo 'hola'.toUpperCase(), sin que arroje un error incluso cuando estas son datos primitivos.

number

A diferencia de otros lenguajes de programación, en JavaScript solo hay una manera de representar cualquier tipo de número, tanto como números enteros como números decimales (mas adelante tocaremos el tema de bigInt )

typeof 42 // 'number'
typeof 12.2 // 'number'
typeof -24 // 'number'

Los números en JavaScript no se "guardan" exactamente como los escribimos nosotros, esto es mas evidente cuando agregamos decimales a los números:

console.log(0.1 + 0.2 === 0.3) // false
console.log(0.1 + 0.2 === 0.30000000000000004) // true???

Ya se, es raro. Pero lo que realmente esta pasando es que JavaScript esta guardando una "aproximación" al numero que hemos escrito. Podemos comprobar esta aproximación con un valor especial que tenemos en JavaScript:

console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991
console.log(Number.MAX_SAFE_INTEGER + 1) // 9007199254740992
console.log(Number.MAX_SAFE_INTEGER + 2) // 9007199254740992 (igual al anterior!)
console.log(Number.MAX_SAFE_INTEGER + 3) // 9007199254740994
console.log(Number.MAX_SAFE_INTEGER + 4) // 9007199254740996
console.log(Number.MAX_SAFE_INTEGER + 5) // 9007199254740996 (el mismo que el anterior!)

Una forma de poder resolver o limitar los problemas que te puedan ocasionar estas aproximaciones, es usando la función toFixed(), que limita la cantidad de decimales de el numero en cuestión.

const nota = 83.3436789
console.log(nota.toFixed(2)) //83.

Numero Especiales

Además de Number.MAX_SAFE_INTEGER, JavaScript tambien tiene otros números especiales:

  • Number.MAX_SAFE_INTEGER
  • Number.MIN_SAFE_INTEGER
  • Number.MAX_VALUE
  • Number.MIN_VALUE
  • Infinity
  • Infinity
  • 0
  • -0
  • NaN: Sí, NaN es de tipo numero

Tenemos estos números disponibles porque JavaScript necesita una manera de representar cualquier operación matemática que quieras hacer, incluso aunque sea "imposible" o "errónea", como por ejemplo 1 / 0 (el resultado es Infinity).

let valor = 0
let a = 1 / valor // Infinity
let b = 0 / valor // NaN
let c = -a // -Infinity
let d = 1 / c // -0

Es poco habitual que tengas la necesidad de usar estos números especiales en tus programas, pero uno que seguro te has encontrado es NaN. Aunque no parezca un número, si que lo es (puedes comprobarlo ejecutando typeof(NaN), te devolverá "number"). Es un número porque JavaScript necesita una manera de representar el resultado de una operación inválida (NaN son las iniciales de "Not a Number"), como por ejemplo 0/0.

Un dato curioso de NaN, es que es el único número en JavaScript que no tiene identidad, lo que ocasiona que nunca será igual a si mismo* (NaN !== NaN)

boolean

Este tipo de dato solo permite dos valores: true o false. Estos valores son habituales usarlos cuando hacemos comparaciones o expresiones en nuestros programas. Dentro de cualquier evaluación (por ejemplo dentro de un if) JavaScript convierte el resultado de la evaluación a boolean.

Todas las operaciones que den como resultado false, '', 0, undefined, null y NaN resolverán la evaluación a false. Los demás valores devolverán true.

null

Este tipo de valor nos ayuda a representar la ausencia de valor. Pero con null tenemos un problema: ¿Por qué la ejecución de typeof null devuelve "object"?

null nos engaña. Así es, es un mentiroso.

Pues eso, null sigue siendo un tipo primitivo, pero recibimos "object" por un error histórico en el lenguaje, que seguramente no va a arreglarse jamás (se intentó solucionar, pero decidieron que era mejor no hacer nada porque rompería muchas páginas webs en el intento 🤷🏼‍♂️)

Este tipo de dato se utiliza mucho para determinar ausencia de valor. Un ejemplo muy claro es en componentes React:

function Button({ hide = false, ...props }) {
return hide ? <button {...props} /> : null // ⬅️ Devolvemos `null` si `hide` es `false`.
}

undefined

Se considera como valor de un dato o variable desconocido. Solo hay un valor con este tipo: undefined.

Siempre que creamos una variable, el primer valor que se le asigna a esa variable es undefined (recuerdas el hoisting?)

Symbol

Este tipo de dato se usa para crear valores únicos, irrepetibles.

const valor1 = Symbol()
const sinNew = new Symbol() // 🚫 no se usa la palabra "new"

Cuando creamos una variable de tipo Symbol, su valor es único, así que solo ese valor sera igual a sí mismo. Aunque creemos otro símbolo a partir del mismo valor, no serán iguales:

Symbol() === Symbol() // false
Symbol(42) === Symbol(42) // false
Symbol("descripcion") === Symbol("descripcion") // false

Registro global de Símbolos

Existe un registro global de símbolos, en el que podemos crear y recibir el mismo símbolo a partir de la descripción. Además que este registro es compartido en nuestra página, incluso entre los iframes y serviceworkers que estén en ella.

var simbolo1 = Symbol.for("escuelafrontend") // Crea el símbolo para ésta descripción si no existe
// en otra parte de nuestro programa
var llave = Symbol.for("escuelafrontend")
simbolo1 === llave // true!

Podemos usar también el método Symbol.keyFor() pasándole el símbolo y descubrir cual es la cadena de texto que lo describe.

¿Para qué puedo usar los Símbolos?

Este tipo no es muy utilizado en nuestros programas, pero si se utiliza mucho dentro de las bibliotecas de frontend que usamos habitualmente. Igualmente este tipo de datos los puedes usar en los siguientes casos:

  • Para substituir el uso de cadenas de texto como parámetros de una función:

    const ACCIONES = {
    ABRIR: Symbol("abrir"),
    CERRAR: Symbol("cerrar"),
    }
    function reducerGrifo(accion) {
    if (accion === ACCIONES.ABRIR) {
    console.log("abrir el grifo")
    return
    }
    if (accion === ACCIONES.CERRAR) {
    console.log("cerrar el grifo")
    return
    }
    console.log("acción desconocida")
    return
    }
    // =============
    reducerGrifo(ACCIONES.ABRIR) // abrir el grifo
    reducerGrifo(ACCIONES.CERRAR) // cerrar el grifo
    reducerGrifo("ABRIR") // acción desconocida

    En este caso, no se puede pasar una cadena de texto a esta función, solo los símbolos del objeto de acciones.

  • Para evitar la colisión de atributos de un objeto cuando estemos haciendo comprobaciones sobre el:

    const persona = {
    nombre: "Horacio",
    apellido: "Herrera",
    }
    const isLoggedIn = Symbol("esta logado")
    persona[isLoggedIn] = false
    Object.keys(persona) // ["nombre", "apellido"]

    En este caso vemos que la propiedad creada con el símbolo está "oculta", ya que no aparece cuando hacemos un "lookup" por las propiedades del objeto. Así si el objeto ya tiene o le asignamos una propiedad con el mismo valor (persona.isLoggedIn) no creará colisiones de nombres.

bigInt

Este tipo de dato nos permite usar cualquier numero entero sin límite de tamaño. Es decir que ya no tenemos la limitación que encontramos con el tipo "number" anteriormente mencionado.

Para poder declarar un valor de tipo bigint, necesitamos agregar una n al final del número deseado:

const numeroMuyGrande = 123456789n // la "n" al final es importante!

En cuanto a operaciones matemáticas, no se permite hacerlas entre diferentes tipos de datos, solo se puede si ambos números son del tipo bigint:

const n1 = 67890987654323456789n
const n2 = BigInt(42) // también podemos crearlos a partir del objeto "BigInt"
n1 + 100 // 🚫 INVÁLIDO
n1 + n2 // ✅

object

Este no lo he mencionado en la lista, pero hacer la distinción no está de más 😉

Básicamente, TODO lo que no sea un valor primitivo, es un objeto. Así que tanto arreglos como funciones son considerados "Objetos". Lo que si es verdad es que podemos considerar los arreglos y las funciones como "sub-tipos de objetos", ya que estos se comportan como tal, pero tienen propiedades y comportamientos especiales:

const array = [1, 2, 3]
function soyUnaFuncion() {
// ...
}
typeof array // "object"
typeof soyUnaFuncion // "function" ????

El resultado de typeof Function no es "object", porque las funciones se consideran "callable objects" o "objetos invocables". Por eso es que su "tipo" devuelve el valor "function". Esto aunque parece que es un error, personalmente creo que esto puede ser una ventaja para nuestros programas. Lo importante es saberlo, así podemos sacarle provecho!

Como podemos sacarle ventaja a los valores primitivos?

Es importante tener estos conceptos claros, porque como mencionaba en la analogía de la comida, conocer bien las herramientas que tenemos a nuestro alcance y como funcionan es muy importante para escribir programas de calidad y evitar bugs.

Desde mi punto de vista la mejor manera de sacarles ventaja es teniendo en cuenta su funcionamiento para escribir mejor código y que esto nos permita ahorrar tiempo cuando ocurran errores o bugs (que lastimosamente son inevitables!).

Autoboxing o "Object Wrapper"

El hecho que podamos llamar a funciones a partir de tipos de datos primitivos no es porque estos datos tengan acceso a esas funciones, sino por "Autoboxing".

Autoboxing o Object Wrapper ocurre cuando llamamos a un método definido en el prototipo relacionado a el primitivo en cuestión.

var saludo = "Hola escuelafrontend!"
saludo.toUpperCase() // "HOLA ESCUELAFRONTEND!"

En el ejemplo de arriba, el método toUpperCase esta definido en el prototipo String, que esta relacionado con el valor primitivo de "saludo". En el momento que llamas a el método del prototipo, el motor de JavaScript encapsula el valor del primitivo, ejecuta la función y destruye este objeto utilizado.

Desafió

Ahora que ya hemos visto todo lo que necesitamos saber sobre primitivos, pongámoslo en práctica!

Miremos el siguiente código:

var root
var primero = 20
var segundo = primero
primero = 25
var v1 = segundo / 0
var v2 = "42"
var v3 = v2 + primero
var logado = Symbol("logado")
var user = {
name: "Horacio",
logado: true,
}

Después de que el código se ejecute completamente:

  1. ¿Que valor tiene la variable segundo: 20, 25 o null?
  2. ¿Que valor tiene la variablev3: 62, "62" o "4225"?
  3. ¿Que valor tiene user.logado: true, Symbol(logado) o false?

Descubre las respuestas mas adelante

Credito Extra

  1. Haz una lista con los valores finales para todas las variables

Conclusión

Los tipos primitivos son una parte fundamental del lenguaje de JavaScript. Saber qué son, cuales son estos tipos y la diferencia entre ellos te va a ayudar a crear un conocimiento fundamental muy importante para tu futura carrera como desarrollador.

Espero que te haya ayudado a entender mejor los tipos de datos primitivos, y que de ahora en adelante lo que muchos describen como "las partes malas de JavaScript", no las veas tan malas y puedas usarlas en tu ventaja.

Respuestas

  1. El valor de la variable segundo es el numero 20
  2. El valor de la variable v3 es "4225"
  3. El valor seria True

Notas

(*) - Una razón por la cual se dice que typeof null devuelve "object" es porque todo objeto en JavaScript se deriva de null, por eso es que devuelve "object" en vez de "null" como la regla dice que debería ser.

fuente: MDN

Referencias

Este video me ayudó MUCHO para refrescar todo lo que te he comentado anteriormente!.

ULTRA recomendado! ⬆️

Artículos Relacionados

¿Quieres mejorar tus habilidades de frontend?