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
- Los valores primitivos son inmutables.
- No se pueden declarar propiedades a valores primitivos.
- 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:
tslet str = "yay"; console .log (str [0]); // yconsole .log (str .length ); // 3 str [0] = "p";Index signature in type 'String' only permits reading.2542Index signature in type 'String' only permits reading.console .log (str ); // yay
No se puede modificar un valor primitivo
tslet str = "yay"; console .log (str [0]); // yconsole .log (str .length ); // 3 str [0] = "p";Index signature in type 'String' only permits reading.2542Index signature in type 'String' only permits reading.console .log (str ); // yay
No se puede modificar un valor primitivo
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.
tsconsole .log (new String (str )[0]); // y console .log (new String (str ).length ); // 3 new String (str )[0] = "p";Index signature in type 'String' only permits reading.2542Index signature in type 'String' only permits reading. console .log (str ); // 3
Mismo error que en el ejemplo anterior
tsconsole .log (new String (str )[0]); // y console .log (new String (str ).length ); // 3 new String (str )[0] = "p";Index signature in type 'String' only permits reading.2542Index signature in type 'String' only permits reading. console .log (str ); // 3
Mismo error que en el ejemplo anterior
Pasos:
- Se define una variable
str
de valor primitivo string. - 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 resultadoy
. - Hace lo mismo del paso anterior, pero accede a la propiedad
length
. - Se crea otro objeto de la variable
str
donde la key 0 es igual ap
. Es como escribir 1+1 en medio de una ejecución. - 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.
ts10 === 10; // true"ave" === "ave"; // true {} === {}; // falseThis condition will always return 'false' since JavaScript compares objects by reference, not value.
Expression expected.2839
1109This condition will always return 'false' since JavaScript compares objects by reference, not value.
Expression expected.[] === [] ; // falseThis condition will always return 'false' since JavaScript compares objects by reference, not value.2839This condition will always return 'false' since JavaScript compares objects by reference, not value.
new Object() es equivalente a {}
ts10 === 10; // true"ave" === "ave"; // true {} === {}; // falseThis condition will always return 'false' since JavaScript compares objects by reference, not value.
Expression expected.2839
1109This condition will always return 'false' since JavaScript compares objects by reference, not value.
Expression expected.[] === [] ; // falseThis condition will always return 'false' since JavaScript compares objects by reference, not value.2839This condition will always return 'false' since JavaScript compares objects by reference, not value.
new Object() es equivalente a {}
¿Qué significa que los objetos se comparan por referencia? Tomemos el siguiente ejemplo:
tslet obj = {};let dos = obj ;let obj2 = {};obj === dos ; // trueobj === obj2 ; // false
tslet obj = {};let dos = obj ;let obj2 = {};obj === dos ; // trueobj === 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.
javascript#[ ] === #[ ]; // true#{} === #{}; // true
javascript#[ ] === #[ ]; // 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:
tsconst resultado = true + 5; // 6Operator '+' cannot be applied to types 'boolean' and 'number'.2365Operator '+' cannot be applied to types 'boolean' and 'number'.
tsconst resultado = true + 5; // 6Operator '+' cannot be applied to types 'boolean' and 'number'.2365Operator '+' cannot be applied to types 'boolean' and 'number'.
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.
tsconst resultado = ["abc"] + "abc"; // abcabc
tsconst resultado = ["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:
javascriptconst resultado = 50 / "5"; // 10
javascriptconst resultado = 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í.
ts54 + "abc"; // "54abc"54 + ""; // "54"
ts54 + "abc"; // "54abc"54 + ""; // "54"
La coerción de booleanos sucede cuando se intenta, comparar o hacer alguna operación lógica.
ts0 || 5; // 5
ts0 || 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:
Igualdad | Representación |
---|---|
Abstracta | a == b (doble igual) |
Estricta | a === b (triple iguales) |
Mismo valor | Object.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.
ts["abc"] == "abc"; // trueThis comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.2367
2839This comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.
ts["abc"] == "abc"; // trueThis comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.2367
2839This comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.
Para tener una idea más clara de todos los resultados dependiendo de la operación puedes ver la siguiente tabla:
Tabla de igualdad de JavaScript1
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.
ts["abc"] === "abc"; // falseThis comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.2367
2839This comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.
ts["abc"] === "abc"; // falseThis comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.2367
2839This comparison appears to be unintentional because the types 'string[]' and 'string' have no overlap.
This condition will always return 'false' since JavaScript compares objects by reference, not value.
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.
tsNaN === NaN ; // falseThis condition will always return 'false'.2845This condition will always return 'false'.Object .is (NaN , NaN ); // true-0 === 0; // trueObject .is (-0, 0); // false0 === -0; // trueObject .is (0, -0); // false
tsNaN === NaN ; // falseThis condition will always return 'false'.2845This condition will always return 'false'.Object .is (NaN , NaN ); // true-0 === 0; // trueObject .is (-0, 0); // false0 === -0; // trueObject .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.
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.
javascriptfunction Persona (edad ) {}//Declarar métodosPersona .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étodosEstudiante .prototype = Object .create (Persona .prototype , { decirNombre : { value : function decirNombre () { console .log ("Mi nombre es", this.nombre ); }, },});Estudiante .prototype .constructor = Estudiante ; // Crear instancias de la claselet estudiante = new Estudiante ("Juanito", 17);estudiante .decirNombre ();estudiante .permisos ();
javascriptfunction Persona (edad ) {}//Declarar métodosPersona .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étodosEstudiante .prototype = Object .create (Persona .prototype , { decirNombre : { value : function decirNombre () { console .log ("Mi nombre es", this.nombre ); }, },});Estudiante .prototype .constructor = Estudiante ; // Crear instancias de la claselet 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.
javascriptclass 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 instancias de la claselet estudiante = new Estudiante ("Marco", 17);estudiante .decirNombre ();estudiante .permisos ();
javascriptclass 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 instancias de la claselet 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.
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.
Referencias
-
Dorey Equality in JavaScript ↩