Apología de un lenguaje ambiguo: El meme definitivo para entender (o no) JavaScript avanzado 😎
Tags: javascript, webdev, spanish, motivation
Published: 2022-02-11 15:03:37

Hace poco encontré en internet un meme de javascript que me pareció super interesante. Por este motivo intentaré explicar en este post por qué javascript puede llegar a ser tan raro.

El meme per se es:

![img](https://res.cloudinary.com/practicaldev/image/fetch/s--ZDtqrBOj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://github.com/damiancipolat/js_vs_memes/blob/master/doc/js_thanks.png%3Fraw%3Dtrue)

---

#**Índice**   
1. [typeof NaN es number](#id1)
2. ["Problemas" de redondeo en enteros y decimales](#id2)
3. [Math.max(), Math.min() y el problema de los Infinity](#id3)
4. [¿Sumas o concatenaciones? Arreglos y objetos](#id4)
5. [Operaciones con booleanos](#id5)
6. [Que carajos es (! + [] + [] + ![]).length ](#id6)
7. [Sumas y concatenaciones de enteros](#id7)
8. [Truthy vs Falsy](#id8)
9. [Conclusiones](#id9)
10. [Referencias](#id10)


## **1. typeof NaN es number** <a name="id1"><a/>
El **operador unario** `typeof` regresa el tipo de dato de la variable a la que se lo aplique.
La siguiente tabla resume todos los posibles casos con los que nos podemos encontrar:

| Valor                  | Resultado   |
| ---------------------- | ----------- |
| Variables no declaradas | "undefined" |
| `undefined`            | "undefined" |
| `null`                 | "object"    |
| `Booleans`             | "boolean"   |
| `Numbers`              | "number"    |
| `Strings`              | "string"    |
| `Functions`            | "function"  |
| `Symbols`              | "symbol"    |
| Cualquier otro valor   | "object"    |


Ahora bien, porque cuando intentamos obtener el tipo de un `NaN` el resultado es `number`:

```js
console.log(typeof NaN); // number
```

Encontré algunas respuestas en foros que me resultaron algo ambiguas, tratare de explicarlo con palabras simples:

Primero, **¿Qué es, o mejor dicho que hace que un valor sea considerado `NaN`?**

En muchas ocasiones te debiste haber encontrado con un `NaN` al hacer alguna operación en una calculadora científica, entonces, `NaN` no es un valor propio de javascript, este concepto va más lejos del lenguaje de programación como tal, puede ser considerado y definido de manera técnica desde un punto de vista netamente matemático, pero justamente para no caer en tecnicismos y continuar con la simplicidad que pretendo podemos quedarnos con que javascript nos arrojará un `NaN` en los siguientes casos:

* Cualquier división entre `0`.
* División de infinito entre infinito.
* Multiplicación de infinito por `0`.
* Cualquier operación que tenga a `NaN` como operando.
* Conversión de un `string` no numérico o un `undefined` a `number`.
* Cualquier valor numérico que no esté incluido en el intervalo de números soportado por el lenguaje.

Acá algunos ejemplos para ilustrar mejor lo antes dicho:

```js
console.log(typeof 5/0); //NaN
console.log(typeof Infinity / Infinity); //NaN
console.log(typeof Infinity * 0); //NaN
console.log(typeof [] - NaN); //NaN
console.log(Number("hola")); //NaN
console.log(Number(undefined)); //NaN
console.log((3.2317006071311 * 10e616) / (3.2317006071311 * 10e616)); // NaN
```

Con todo esto solo sabemos cuándo en javascript es un valor `NaN`, ahora veremos por qué `NaN` es de tipo `number`.

La respuesta es más sencilla de lo que parece, el estándar ECMAScript, quienes dan mantenimiento al lenguaje, estableció que para **cualquier valor numérico** los datos deben adaptarse al estándar **IEEE-754**, esto a groso modo indica que los números en javascript deben ser de **punto flotante**, deben incluir `Infinity` y `-Infinity` y (ohh sorpresa) también el valor `NaN`.

Si vemos un poco más de cerca el conjunto ejemplos de la parte superior, podemos darnos cuenta que un `NaN` aparece cuando intentamos realizar algún tipo de operación con números, este es el común denominador que comparten todos los ejemplos, de alguna u otra manera al manipular números bien sean como simples valores primitivos, usando `Infinity`, `-Infinity` o el propio `NaN` (ahora sabemos que estos 3 por el estándar **IEEE-754** se relacionan de manera directa con los números del lenguaje) es donde `NaN` se origina. Esto tiene todo el sentido del mundo.

En cuanto al caso de:

```js
console.log((3.2317006071311 * 10e616) / (3.2317006071311 * 10e616)); // NaN
```

En aritmética tradicional el resultado esperado de esta operación sería `1` porque tanto el numerador como el denominador de la operación es el mismo.

Es bastante peculiar ya que a simple vista es una operación que se debería resolver ¿verdad?

El problema acá radica en que javascript solo soporta números dentro de un intervalo específico, si algún dato sale de este intervalo entonces el intérprete se queja arrojando un `NaN`.

> _La aritmética informática no puede operar directamente con números reales, sino solo con un subconjunto finito de números racionales, limitado por la cantidad de bits utilizados para almacenarlos._

Para saber el máximo y el mínimo de valores que javascript puede aceptar podemos hacer lo siguiente:

```js
console.log(Number.MIN_VALUE); // 5e-324
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308
```

Como los valores del ejemplo exceden estos límites, javascript dice algo como: _hey! estos números que quieres que divida son extremadamente grandes para mi, así que los redondeará a `Infinity`, pero `Infinity / Infinity` da `NaN`, entonces te muestro un `NaN`. Gracias por usar JavaScript!_

**En conclusión**, por el estándar **IEEE-754** los valores `NaN`, `Infinity` y `-Infinity` están ligados directamente a los números en javascript; por este motivo al intentar obtener el tipo de dato de cualquiera de estos valores obtendremos `number`.

```js
console.log(typeof NaN); //number
console.log(typeof Infinity); //number
console.log(typeof -Infinity); //number
```

Espero que haya quedado claro.

## **2. "Problemas" de redondeo en enteros y decimales** <a name="id2"><a/>
**¿Por qué `9999999999999999` (son `16` `9`'s) redondea a `10000000000000000`?**

¿Por que `0.1 + 0.2 == 0.3` nos da `false`?

¿Por que `0.5 + 0.1 == 0.6` nos da `true`?

Nuevamente al intentar hacer operaciones que para un ser humano parecen lógicas, Javascript termina sorprendiendo y nos arroja resultados que nos provocan más de un dolor de cabeza.

Para comprender el por que de estas preguntas debemos remitirnos una vez más al estándar **IEEE-754** que usa el lenguaje para la manipulación de valores numéricos.

Según este estándar, **javascript almacena los números en un formato de 64 bits** también llamado formato de **doble presión**:

* El primer bit se reserva para el signo del número
* `11` bits almacenan la posición del punto flotante.
* `52` bits el número como tal.

Dando un total de `64` bits por cada número guardado en memoria.

El número `9999999999999999` cuando lo convertimos a su forma binaria e intentamos almacenarlo en 64 bits se desborda, es muy grande para guardarlo con exactitud, entonces javascript nuevamente en vez de lanzarnos un error hace internamente un proceso de redondeo que no vemos e intenta lanzarnos un resultado más o menos lógico. Obviamente no es el resultado que esperábamos.

Con el caso de los decimales pasa algo similar.

En el ejemplo:

```js
console.log(0.1 + 0.2 == 0.3); // false
```

Solo las fracciones con un **denominador que es una potencia de dos pueden representarse finitamente en forma binaria**. Dado que los denominadores de `0,1` (`1/10`) y `0,2` (`1/5`) no son potencias de dos, estos números no se pueden representar de forma finita en un formato binario.
Entonces Javascript nuevamente tendrá que realizar un proceso de redondeo implícito para intentar arrojar un resultado más o menos lógico, es en este proceso de redondeo que se pierde presión.

En el ejemplo:
```js
console.log(0.5 + 0.1 == 0.6); // true
```
`0.5` en fracción es `1/2` el denominador si es potencia de 2 entonces al el número `0.5` puede ser almacenado de manera precisa en formato binario en memoria.

> _El algoritmo que usa **IEEE-754** para convertir los números en javascript a formato binario de 64 bits es bastante complejo de explicar en pocas palabras, dejare links en las referencias de este post por si deseas saber mas el respecto._

En conclusión, estas operaciones raras en javascript se dan por el uso y aplicación del estándar **IEEE-754**. Puede resultar muy confuso para el ser humano, pero las computadoras lo entienden muy bien. Por motivos como estos muchos desarrolladores experimentados recomiendan tratar siempre de usar números enteros en los programas y evitar siempre que sea posible las operaciones con números decimales.

## **3. Math.max(), Math.min() y el problema de los Infinity** <a name="id3"><a/>
`Math.max()` y `Math.min()` son 2 maneras de encontrar el máximo y el mínimo de una lista de números. Es muy sencillo de comprender.

¿Cómo podemos saber si un número es mayor o menor a otro? No hace falta más que compararlos.

Si tenemos algo como esto:

```js
console.log(Math.max(5)); //5
```

El resultado será a fuerza `5` ya que como no hay contra qué comparar, entonces devolvemos el único número.

Entonces qué pasa si hacemos esto:

```js
console.log(Math.max()); // -Infinity
```

La respuesta textual la hallamos en la MDN:

> `-Infinity` **es el comparador inicial** porque casi todos los demás valores son mayores, por eso cuando no se dan argumentos, se devuelve `-Infinity`.

Como no tenemos nada que comparar, JavaScript toma el valor más pequeño posible como valor por defecto o comparador inicial.

Entonces, `Math.max()` comienza con un valor de búsqueda de `-Infinity`, porque cualquier otro número será mayor que `-Infinity`.

```js
console.log(Math.max()); // el dev ve esto
console.log(Math.max(-Infinity)); // javascript ve esto
```

Esta misma lógica sirve para:

```js
console.log(Math.min()); // Infinity
```

Como no hay ningún valor para comparar javascript usa el valor más grande posible que es `Infinity` como comparador inicial o valor por defecto.

```js
console.log(Math.min()); // el dev ve esto
console.log(Math.min(Infinity)); // javascript ve esto
```

## **4. ¿Sumas o concatenaciones? Arreglos y objetos** <a name="id4"><a/>

Aplicar el operador `+` entre arreglos y objetos es de lo más confuso que puede existir en javascript.
Para poder entender el porqué de los resultados extraños es menester entender primero como javascript convierte **objetos primitivos.**

Pasos para la conversión de un objeto a un primitivo:
1. Si la entrada es un primitivo, entonces devolvemos el mismo valor.
2. De lo contrario la entrada es un objeto, entonces aplicamos el método `valueOf`. Si el resultado es primitivo, lo devolvemos.
3. De lo contrario llamamos al método `toString`. Si el resultado es primitivo, lo devolvemos.
4. De lo contrario, devolvemos un `TypeError`.

Vamos los ejemplos del meme:

### **[] + []**
Cuando intentamos realizar esta operación, el resultado es una cadena vacía `""`.
Apliquemos los pasos de conversión de objeto a primitivo (recuerda que los arreglos en javascript son considerados de tipo objeto):

* Ninguna de las entradas es un primitivo.
* Aplicamos el método `valueOf`:

```js
console.log([].valueOf()); // []
```
Aún seguimos obteniendo el mismo arreglo vacío.

* Aplicamos el método `toString`:

```js
console.log([].toString()); // ""
```
Ahora obtenemos un arreglo vacío, entonces al intentar realizar `[] + []` es como si intentáramos concatenar dos cadenas vacías `"" + ""` que nos dará como resultado otra cadena vacía.

```js
console.log([].toString() + [].toString()); //""
```

### **[] + {}**

Ahora intentamos concatenar un arreglo con un objeto, ambos vacíos.

Ya sabemos que el arreglo vacío convertido a primitivo es una cadena vacía, entonces intentemos aplicar los pasos de conversión al objeto.

* El objeto no es un primitivo.
* Aplicamos el método `valueOf`:

```js
console.log({}.valueOf()); // {}
```

Obtenemos el mismo objeto vacío.

* Aplicamos el método `toString`:

```js
console.log({}.toString()); // "[object Object]"
```

Al convertir un objeto a primitivo obtenemos `"[object Object]"`

La operación entonces se vería así:

```js
console.log("" + "[object Object]"); // "[object Object]"
```

### **{} + []**

Ahora intentamos realizar la concatenación de un objeto con un arreglo, ambos vacíos.

Lo lógico acá es pensar en la clásica propiedad asociativa de la adición, ¿si `[] + {}` es `"[object Object]"` entonces `{} + []` debería ser igual no? Lastimosamente no es así.

En este caso el objeto es el primer operando de la operación, pero javascript no lo toma como objeto, sino como un bloque de código vacío:

```js
{

}

+[]
```

Entonces cómo ejecutamos el código de arriba a abajo, el intérprete entra y sale del bloque vacío, nos queda `+[]`.
Por sino lo sabias el operador `+` es un _shorthand_ de `Number`, entonces podemos convertir a `number` usando este operador.

Ya sabemos que `[]` es igual a una cadena vacía, y una cadena vacía es un valor `falsy`, por ello al convertirlo a `number` tendremos `0`.

Este comportamiento es posible cambiarlo si agrupamos la operación para formar una expresión, de la siguiente manera:

```js
({} + []) // "[object Object]"
```

o si usamos variables para realizar la operación:

```js
const obj = {};
const arr = [];

console.log(obj + arr); // "[object Object]"
```

## **{} + {}**
Muy similar al último ejemplo, pero el resultado de concatenar 2 objetos vacíos dependerá de en qué navegador lo ejecutes.

En Mozilla:
Nuevamente el primer operador no será evaluado como objeto sino como bloque vacío de código. Entonces nos queda `+{}`, el resultado de convertir un objeto a número es `NaN`.

En Chrome:
Evalúa toda la operación como una expresión, ya sabemos que un objeto vacío convertido a primitivo es `"[object Object]"`, solo tendríamos que concatenarlo y el resultado serie `"[object Object][object Object]"`.

## **5. Operaciones con booleanos** <a name="id5"><a/>
### **true + true + true === 3**
Por aserciones de tipos `true` se convierte en `1`.
Entonces tendríamos `1 + 1 + 1 === 3`.
El operador `===` compara tanto el valor como el tipo de dato, `3 === 3` daria `true`.

### **true - true**
Nuevamente por aserción de tipos, `true` adopta el valor de `1`.
Entonces tendríamos `1 - 1` dando como resultado `0`.

### **true == 1**
El operador `==` solo compara el valor, tendríamos `1 == 1`, el valor es el mismo, entonces el resultado sería `true`.

### **true === 1**
El operador `===` compara tanto el valor como el tipo de dato. Entonces un operando es `boolean` y el otro `number`, por ello el resultado sería `false`.

## **6. Que carajos es (! + [] + [] + ![]).length** <a name="id6"><a/>

Si ver este ejercicio ya resulta un poco intimidante, el resultado es más risible que sorprendente. Aunque no lo creas la respuesta para esta operación es `9`.

Para comprender bien cómo se llega a esa respuesta, debemos parcelar el ejercicio:

```js
//En vez del ejercicio original:
console.log((! +[] + [] + ![]).length)

//Podemos escribirlo de la siguiente manera:
console.log(( (! + []) + [] + (![]) ).length)

```
Aquí diferenciamos 3 operaciones separadas bien definidas:
* `! + []`
Ya sabemos que un arreglo convertido a primitivo da una cadena vacía `""`, el operador `+` convertirá la cadena vacía a tipo `number`, como una cadena vacía es un **valor falsy** la conversión nos dará `0`; finalmente negamos el cero `!0`, cero también es un **valor falsy** por ende negado sería `true`.
Visto en código sería algo así:

```js
console.log(! + []); // true
console.log(! + ""); // true
console.log(! + 0); // true
console.log(!0); //true
console.log(!false); //true
console.log(true); //true
```

* `[]`
El segundo operando es solo un arreglo vacío, esto llevado a primitivo es `""`.

* `![]`
Finalmente, un arreglo vacío negado. El arreglo vacío es un **valor truthy** y cómo va acompañado del operador de negación el resultado sería `false`.

Después de todas estas operaciones intermedias el ejercicio tendrá la siguiente pinta:

```js
console.log(( true + "" + false).length);
```

Esto es mucho más fácil de operar:
* `true + ""`
Al concatenar `true` con la cadena vacía, el valor booleano se transforma a `string`:

```js
console.log(( "true" + false).length);
```

* `"true" + false`
Otra vez una concatenación de cadena con booleano:

```js
console.log(( "truefalse").length);
```

Para finalizar aplicamos `length` a la cadena dando como resultado el `9` que tanto nos extrañaba al comienzo.

## **7. Sumas y concatenaciones de enteros** <a name="id7"><a/>

### **9 + "1"**
Se intenta sumar un `string` con un `number`, como no es una operación válida javascript intenta hacer su mejor esfuerzo para darnos un resultado lógico, entonces hace lo que que se denomina una **aserción de tipo** y tras bambalinas convierte al `9` en `string`. La suma se vuelve ahora una concatenación de cadenas que da como resultado `"91"`.

```js
console.log(9 + "1"); // El dev ve esto
console.log("9" + "1"); // JavaScript lo interpreta así
```

### **9 - "1"**
El operador `+` sirve para muchas cosas en javascript, desde sumar números, concatenar cadenas hasta convertir valor a tipo `number`.
El operador `-` es más simple, solo se usa para restar números, por ello es que nuevamente acá el lenguaje hace una **aserción de tipos** pero esta vez convierte el `string` `"1"` a tipo `number` dando como resultado `91`.

```js
console.log(91 - "1"); // El dev ve esto
console.log(91 - 1); // JavaScript lo interpreta así
```

## **8. Truthy vs Falsy** <a name="id8"><a/>
Los valores truthy y falsy son básicos en javascript, te dejo unos links en las referencias del post para que puedas saber más de ellos.

## **[] == 0**

Ya sabemos que `[]` convertido a primitivo es `""`.
Tanto `""` como `0` son **valores falsy**.
Usamos el operador `==` u operador de comparación débil entonces solo comparamos los valores mas no los tipos de datos.

```js
console.log([] == 0); // El dev ve esto
console.log(false == false); // JavaScript lo interpreta así
```

## **9. Conclusión** <a name="id9"><a/>
JavaScript puede ser un lenguaje bastante bizarro y por ello mismo muchos devs sobre todo de la vieja escuela pueden tirarle mucho hate, pero cuando uno comprende el porqué de las cosas, cuando vamos a la esencia del lenguaje e intentamos entender por qué las cosas pasan de una determinada manera y no de otra es cuando recién nos damos cuenta los motivos de los hechos.

Espero que el post te haya gustado y sobre todo ayudado en tu carrera profesional.

Nos vemos...

---

## **10. Referencias** <a name="id10"><a/>

* [NaN and typeof](https://javascriptrefined.io/nan-and-typeof-36cd6e2a4e43)

* [Here is what you need to know about JavaScript’s Number type](https://indepth-dev.translate.goog/posts/1139/here-is-what-you-need-to-know-about-javascripts-number-type?_x_tr_sl=auto&_x_tr_tl=es&_x_tr_hl=es&_x_tr_pto=op)

* [Why is 9999999999999999 converted to 10000000000000000 in JavaScript?](https://stackoverflow.com/questions/13429451/why-is-9999999999999999-converted-to-10000000000000000-in-javascript)

* [Why Math.max() is Less Than Math.min() in JavaScript](https://levelup.gitconnected.com/why-math-max-is-less-than-math-min-in-javascript-7aaf2c39ee36)

* [What is {} + {} in JavaScript?](https://2ality.com/2012/01/object-plus-object.html)

* [JavaScript values: not everything is an object](https://2ality.com/2011/03/javascript-values-not-everything-is.html)

* [Why console.log((!+[]+[]+![]).length) gives 9?](https://stackoverflow.com/questions/51010468/why-console-log-length-gives-9)

* [Why (! + [] + [] + ![]).length is 9 ](https://dev.to/tomasforsman/why-length-is-9-2i4l)

* [Truthy](https://developer.mozilla.org/es/docs/Glossary/Truthy)

* [Falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)

---

Algunos posts de mi autoria que probablemente te llamen la atención:

{% link https://dev.to/duxtech/es6-promesas-en-javascript-5bpb %}

{% link https://dev.to/duxtech/webpack-para-torpes-l7b %}

{% link https://dev.to/duxtech/8-herramientas-gratuitas-de-desarrollo-web-frontend-que-posiblemente-no-conocias-46kj %}

---

![Image description](https://media.giphy.com/media/UopqNm6oSd81BDpWrc/giphy.gif)