Tipos y objetos en JavaScript

En JavaScript tenemos varios tipos de datos; booleanos, numéricos, los de texto o string, el null que indica que una variable.

Tipos y objetos en JavaScript

En JavaScript tenemos varios tipos de datos; booleanos, numéricos, los de texto o string, el null que indica que una variable no tiene un valor, undefined que indica que una variable está declarada en memoria, pero su valor aún no ha sido definido y el symbol, el cual fue introducido en la definición de EcmaScript 6. A partir de aquí todo lo demás son objetos, como son los arreglos o arrays, las fechas o incluso objetos que tú mismo declares.

Diferencias entre valores primitivos y objetos

  1. Los valores primitivos son inmutables.
  2. No se pueden declarar propiedades a valores primitivos.
  3. Los objetos se manejan por referencias por lo que dos objetos diferentes que tengan los mismos valores no serán iguales.

Para entender esto veamos el siguiente ejemplo:

let str = "yay";
console.log(str[0]); // "y"
console.log(str.length); // 3

str[0] = "p";
console.log(str);

Analizando el código anterior ¿Cuál será el segundo resultado de la consola? De una el resultado sigue siendo "yay", pero ¿Por qué pasa esto?

Primero tenemos que entender que los strings son valores primitivos y como se ha mencionado los valores primitivos son inmutables, o sea que no se pueden modificar. Cuando se intenta acceder a una propiedad de un valor primitivo como en el caso de str[0], JavaScript envuelve el valor en un objeto.

Lo anterior puede resultar un poco confuso, pero veamos cómo sería el código enfocándonos cuando JavaScript envuelve los valores primitivos en un nuevo objeto para que tenga sentido la acción que le damos a realizar.

let str = "yay";
console.log(new String(str)[0]); // "y"
console.log(new String(str).length); // 3

new String(str)[0] = "p";
console.log(str);
  1. Se define una variable str de valor primitivo string.
  2. Se intenta acceder al key 0 de la variable str, como no es posible declarar propiedades a valores primitivos JavaScript lo envuelve en un objeto de tipo string, se accede al valor y lo imprime dando resultado y.
  3. Hace lo mismo del paso anterior, pero accede a la propiedad length.
  4. Se crea otro objeto de la variable str donde la key 0 es igual a p. Es como escribir 1+1 en medio de una ejecución.
  5. Imprime la variable str que es igual a yay.

Cuando comparamos objetos, estos se comparan por referencia. ¿Esto qué significa? Cada objeto, a pesar de que puedan lucir similares o iguales, ellos son diferentes, cada uno tiene su propia identidad. A la hora de comparar dos objetos vamos a obtener un valor negativo, ya que ellos no son iguales. A diferencia los valores primitivos, los cuales ellos no tienen su propia identidad, vamos a obtener un resultado positivo.

Una forma simple de ver que los objetos tienen identificador propio está en palabra new, lo veo como algo que cada vez que se invoca es algo nuevo distinto a lo demás y algo simple de comprobar de una manera más visual es comparando los datos.

10 === 10; // true
"ave" === "ave"; // true
// new Object() es equivalente a {}
{} === {}; // false
[] === []; // false

¿Qué significa que los objetos se comparan por referencia? Tomemos el siguiente ejemplo:

let obj = {};
let dos = obj;
let obj2 = {}
obj === dos // true
obj === obj2 // false

El resultado al comparar obj y dos es true porque en este caso sí es el mismo objeto porque apunta a la misma dirección de la memoria y obj con obj2 es false porque ya no son el mismo objeto.

En caso de que queramos comparar el contenido de arrays u objetos es posible que lo que queramos usar sean los valores primitivos de tuples o records, valores que son primitivos que se añaden recientemente al lenguaje.

#[] === #[] // true
#{} === #{} // true

Coerción de datos

La coerción sucede cuando tenemos que convertir un valor de un tipo de dato a otro tipo de dato. La coerción puede suceder en ciertos escenarios automáticamente debido a que JavaScript es un lenguaje débilmente tipado, por ejemplo:

true + 5 // 6

Aquí estamos sumando el valor booleano true con el número 5 y estamos recibiendo un resultado de 6. ¿Por qué sucede esto? Bueno, sucede porque JavaScript está convirtiendo el valor true a 1 para darle sentido a esta operación. En este caso, true nos retorna un 1. Al sumarse recibimos un 6.

["abc"] + "abc" // abcabc

Aquí estamos sumando un arreglo el cual tiene un elemento de una cadena de texto que es abc con una cadena de texto sin el arreglo, de esta suma recibimos un abcabc. JavaScript convierte el arreglo a un string automáticamente y al sumar ambas cadenas de texto se concatenan.

La coerción numérica, generalmente sucede cuando tú intentas hacer alguna operación matemática, por ejemplo, en este caso:

50 / "5" // 10

Aquí estamos dividiendo un número 50 entre un string que tiene un valor de 5. JavaScript convierte el string 5 a un número. Por lo tanto, recibimos un 10.

La coerción de strings generalmente sucede cuando se utiliza el operador de suma y alguno de los dos valores es un string. JavaScript asume se está intentando concatenar strings, entonces trata de convertir el otro elemento en string y los une, como puedes ver aquí.

54 + "abc" // "54abc"
54 + "" // "54"

La coerción de booleanos sucede cuando se intenta, comparar o hacer alguna operación lógica.

0 || 5 // 5

Aquí tenemos un operador OR, básicamente está diciendo o 0 o 5, y estamos recibiendo 5. El 0 está siendo convertido a un valor false y 5 está siendo convertido a un valor true. El operador OR siempre se va a inclinar por el valor true y por eso recibimos un 5.

Igualdad de valores

En JavaScript existen los siguientes tipos de igualdad:

IgualdadRepresentación
Abstractaa == b (doble igual)
Estrictaa === b (triple iguales)
Mismo valorObject.is(a, b)

La igualdad abstracta es confusa porque al igual que lo vimos en la coerción de datos JavaScript convierte los valores a un tipo que tenga sentido.

["abc"] == "abc" // true

Para tener una idea más clara de todos los resultados dependiendo de la operación puedes ver la Tabla de igualdad de JavaScript:

Tabla de igualdad de JavaScript

Es difícil aprenderse la tabla anterior por lo que hacer este tipo de igualdades puede causar algunos errores en nuestro programa o algunos comportamientos inesperados si olvidamos un dato. ¿Cómo la evitamos?. Lo recomendable es que se use el operador de igualdad estricta. Este operador evita que los valores se conviertan al compararlo uno con el otro.

["abc"] === "abc" // false

Puedes ver que ya obtenemos un valor false, ya que este array no está siendo convertido en un string.

La igualdad del mismo valor con Object.is() es muy similar a la igualdad estricta, con dos casos especiales, Not a Number y la igualdad de cero y cero con signo negativo.

NaN === NaN // false
Object.is(NaN, NaN) // true

-0 === 0 // true
Object.is(-0, 0) // false
0 === -0 // true
Object.is(0, -0) // false

Prototipos

En JavaScript todos los objetos tienen un prototipo. Lo podemos ver en la consola como __proto__, aquí podemos ver todas las funciones y argumentos que se pueden usar y al final de la lista puede que veamos de nuevo a __proto__ hasta llegar al __proto__: Object porque todos los objetos de JavaScript heredan de este objeto creando la cadena de prototipos.

Prototipo

Ventajas de los prototipos:

  • Todas las funciones o propiedades que declaremos dentro del prototipo van a ser heredadas por todas las instancias de esta clase.
  • Todas las instancias de esta clase van a apuntar al mismo prototipo por lo que podemos tener un sinfín de números de instancias, pero solamente un prototipo. Entonces no vamos a sobrecargar la memoria de la computadora.

Para crear una clase se utiliza un objeto función donde se pueden agregan las propiedades y se declaran los métodos utilizando prototype en lugar de como se vio en la consola __proto__. Con esto ya se pueden llamar instancias de la clase.

function Persona(edad){
}
//Declarar métodos
Persona.prototype = {
  permisos: function permisos(){ 
      console.log(`tengo ${this.edad} y ${this.edad < 18 ? "no" : "sí"} puedo votar`);
  }
}
Persona.prototype.constructor = Persona;

function Estudiante(nombre, edad){
  Persona.call(this);
  this.nombre = nombre;
  this.edad = edad;
}
//Declarar métodos
Estudiante.prototype = Object.create(Persona.prototype, {
  decirNombre: {
    value:  function decirNombre(){console.log("Mi nombre es",this.nombre)}
  }
});
Estudiante.prototype.constructor = Estudiante;

// Crear instacias de la clase
let estudiante = new Estudiante("Juanito",17);
estudiante.decirNombre();
estudiante.permisos();

Desde la especificación de ECMAScript 6 se introdujo la sintaxis de las clases. Es una transformación de la sintaxis de prototipos para hacer más cómoda la declaración de clases, syntactic sugar para prototipos.

class Persona {
  //Declarar métodos
  permisos(){ 
    console.log(`tengo ${this.edad} y ${this.edad < 18 ? "no" : "sí"} puedo votar`);
  }
}

class Estudiante extends Persona {
  constructor(nombre, edad) {
    super();
    this.nombre = nombre;
    this.edad = edad;
  }
  //Declarar métodos
  decirNombre(){
    console.log("Mi nombre es:", this.nombre);
  }
}

// Crear instacias de la clase
let estudiante = new Estudiante("Marco", 17);
estudiante.decirNombre();
estudiante.permisos();

Se puede observar las similitudes a los prototipos, se declara la clase, dentro tiene el método constructor donde recibe las propiedades y se declaran los métodos. Ahora al ver la estructura del objeto vemos lo siguiente donde la izquierda es creada en forma de prototipo y la derecha en forma de clase.

Prototipos vs Classes

Se puede observar que la diferencia es que en el constructor una es de tipo función y la otra dice class, pero al ver el tipo se puede ver que por detrás la clase es una función.

Conclusión

Hemos visto el comportamiento de los tipos primitivos y las características de los objetos de JavaScript. Los objetos están por todas partes y los revisamos por encima sin tocar métodos y otros detalles. También vimos la forma tradicional de crear clases y la que fue implementada con ES6.