Ivan Franco

I. Aprendiendo React — Conceptos de JavaScript

Como primer paso para aprender React, es necesario reforzar algunos conceptos del lenguaje JavaScript. La idea de esta serie de historias es poder realizar un paso a paso viendo los temas más importantes para finalmente poder armar una aplicación de ejemplo.

Esta guía que armé, tiene el mismo enfoque que utilicé a la hora de aprender React. Más allá que cada persona tiene distintas formas de aprender (algunas son más teóricas y otras más prácticas) traté de unir ambos mundos para que pueda servirle a cualquier lector.

JS logo

¿Por qué es necesario revisar conceptos del lenguaje JavaScript?

Es muy común aprender JavaScript de forma autodidacta y muy enfocada a resolver problemas particulares. Esto quizás es debido a que históricamente, se lo tomaba a JavaScript como un lenguaje secundario (para hacer alguna acción en la interacción con el usuario). Era poco probable, detenerse a aprender el lenguaje como tal vez estamos acostumbrados con otros.

Lo insólito es que es uno de los lenguajes de programación más utilizados en el mundo y a la vez el más incomprendido.

JavaScript es usado actualmente por una cantidad enorme de aplicaciones, demostrando que tener un conocimiento más profundo de la tecnología es una habilidad importante para cualquier desarrollador web o mobile.

¿Qué es JavaScript?

JavaScript es un lenguaje dinámico y multi-paradigma con tipos y operadores. Su sintaxis está basada en los lenguajes Java y C.

JavaScript soporta tanto el paradigma orientado a objetos (con objetos prototipados en lugar de clases) como también el paradigma funcional (dado que las funciones las toma como objetos, pueden ser almacenadas en variables y pasarlas como cualquier otro objeto).

JS machine

Vamos a comenzar viendo los tipos que tiene JavaScript:

  • Number
  • String
  • Boolean
  • Symbol (nuevo en ES6)
  • Object
  • Function
  • Array
  • Date
  • RegExp
  • null
  • undefined

Numbers

Los números en JavaScript son, de acuerdo a las especificaciones del lenguaje, “double-precision 64-bit format IEEE 754 values”. Esto tiene sus consecuencias, no existe el tipo primitivo integer como estamos acostumbrados a otro lenguajes, razón por la cual, tenemos que tener un poco de cuidado a la hora de realizar operaciones aritméticas.

Si abrimos la consola del explorador (F12 para Chrome) y ejecutamos la siguiente operación, obtendremos un resultado que a priori no esperamos:

> 0.1 + 0.2
< 0.30000000000000004

¿Qué es lo que pasó? internamente, las computadores usan un formato (binario punto flotante) donde no pueden representar en forma precisa un número de este estilo. Cuando el código es interpretado, el 0.1 ya es redondeado al número más cercano en dicho formato (el cual ya tiene un pequeño error de redondeo). Esto es independiente del lenguaje pero JavaScript no hace un redondeo para mostrar una representación aceptable del resultado.

Numbers

Por otro lado, los operadores aritméticos comunes están soportados, como la suma, resta, resto, multiplicación, división. Además, existe un built-in object llamado Math que provee de funciones y constantes matemáticas:

> Math.sin(3.5);
< -0.35078322768961984
> Math.PI;
< 3.141592653589793

Se puede convertir un string a un número utilizando la función integrada parseInt() Como segundo parámetro opcional se puede indicar la base de conversión:

> parseInt('123', 10);
< 123
> parseInt('010', 10);
< 10

En el caso que el string comience con 0x lo tomará automáticamente como hexadecimal a convertir

> parseInt('0x10');
< 16

Si se desea convertir un binario a número, solo es necesario cambiar la base:

> parseInt('11', 2);
< 3

Si se quiere convertir a punto flotante se puede utilizar la la función integrada parseFloat(), tal como parseInt(). La única diferencia es que siempre se utiliza la base 10.

Por otro lado, se puede utilizar el operador unario + para convertir valores a números:

> + '42';
< 42> + '010';
< 10> + '0x10';
< 16

Un valor especial llamado NaN (“Not a Number”) es devuelto si el string no es numérico:

> parseInt('hola', 10);
< NaN

Si se realizan operaciones matemáticas con NaN el resultado siempre va a terminar siendo NaN:

> NaN + 5;
< NaN

Para verificar si un valor es NaN, se puede utilizar la función integrada isNaN(). JavaScript además utiliza otros valores especiales como Infinity y -Infinity:

> 1 / 0;
< Infinity
> -1 / 0;
< -Infinity

Existe otra función integrada de JavaScript que verifica si el valor es finito:

> isNaN(NaN);
< true
> isNaN(123);
< false
> isFinite(1/0);
< false
> isFinite(-Infinity);
< false
> isFinite(NaN);
< false;

Por último, para diferenciar el funcionamiento entre parseInt(), parseFloat() y + radica en que las dos primeras funciones va convirtiendo el string hasta alcanzar un caracter no válido. Mientras que por el contrario, el operador unario devuelve NaN en el caso que haya un caracter inválido:

> parseInt('10.2abc');
< 10
> parseFloat('10.2abc');
< 10.2
> + '10.2abc';
< NaN

Strings

Los strings en JavaScript son secuencias de caracteres Unicode (UTF-16). Para encontrar la longitud de un string se puede utilizar la propiedad length:

> 'hola'.length;
< 4

Este es nuestro primer contacto con objetos de JavaScript. Los strings pueden ser usados como objetos también, tienen métodos que permiten manipular el string y acceder a su información.

> 'hola'.charAt(0);
< "h"
> 'hola, mundo'.replace('mundo', 'universo');
< "hola, universo"
> 'hola'.toUpperCase();
< "HOLA"

Otros tipos (falsy values)

JavaScript distingue entre null, que es un valor que indica un valor inexistente (y es únicamente accesible a través de la palabra null) y undefined, que es un valor de tipo undefined que indica un valor no instanciado. Esto significa, un valor sin asignar todavía.

Más adelante veremos con mayor detalle las variables, pero como comentario, en JavaScript es posible declarar una variable sin asignarle un valor. Si hacemos esto, la variable tendrá el tipo undefined (la cual es una constante).

True or false

Por otro lado, JavaScript tiene un tipo boolean, que permite los valores true y false (ambos son palabras reservadas). Cualquier valor puede ser convertido a un valor booleano teniendo en cuenta las siguientes reglas:

  1. false, 0, strings vacíos (""), NaN, null y undefined todos son convertidos a false.
  2. Cualquier otro valor se convierte a true.

Todos los valores definidos en el punto 1, son conocidos como falsy values. Se puede realizar esta conversión en forma explícita usando la función Boolean():

> Boolean('');
< false
> Boolean(234);
< true

De todas formas, esto no es necesario, dado que JavaScript realizará esta conversión cuando espere un booleano. Existen operadores booleanos como && (AND lógico), || (OR lógico) y ! (NOT lógico), los cuales veremos con mayor detalle más adelante.

Variables

Originalmente, la única forma de declarar una variable era utilizando la palabra reservada var. Luego, con la llegada de ECMAScript 2015(conocido como ES6) aparecieron let y const — voy a hacer el próximo post dedicado únicamente a estas nuevas funcionalidades que vinieron con ES6.

Una variable declarada con la palabra reservada var está disponible fuera de la función donde fue declarada. Un ejemplo del scope para una variable var:

// mi variable VariableVar ES visible acá
for(var VariableVar = 0; VariableVar < 5; VariableVar++){
  // mi variable VariableVar es visible para toda la función
}
// mi variable VariableVar ES visible acá

Con la utilización de var, a diferencia de otros lenguajes como Java o C#, el scope lo da la función y no el bloque de código. Es decir, que si una variable es definida utilizando var en un for (como el ejemplo anterior), será visible en la función que la contiene. No obstante, volviendo a remarcarlo, esto fue modificado con ES6 con la aparición de let y const, las cuales permiten declaraciones de variables con un alcance dentro de un bloque de código.

Operadores

Los operadores numéricos de JavaScript son +, -, *, /, % (que es la función resto). Los valores son asignados con = y además hay asignaciones compuestas tales como += y -= que equivalen a x = x + y y x = x - y respectivamente.

Además, se pueden utilizar los operadores ++ y -- para incrementar y decrementar como prefijo o sufijo.

Por otro lado, el operador + también realiza la concatenación de strings:

> 'hola' + 'mundo';
< "hola mundo"

Si se suma un string con un número (u otro valor), todo es convertido primero a string:

> '3' + 4 + 5;
< "345"
> 3 + 4 + '5';
< "75"

Las comparaciones en JavaScript se pueden hacer usando <, >, <= y >=. Los mismos se pueden utilizar tanto con números como con strings. Sin embargo, la igualdad es un poco más compleja. El doble igual (==) realiza coerción de tipos (obliga a un objeto a que se comporte como otro tipo):

> 123 == '123;
< true> 1 == true;
< true

Para evitar la coerción, se debe utilizar el triple igual. El cual no sólo realiza una comprobación del valor sino también del tipo.

> 123 === '123';
< false
> 1 === true;
< false

Por último, el operador de distinto funciona de la misma manera que el igual (!= y !==).

Estructuras de control

JavaScript cuenta con un set de estructuras de control similar a otros lenguajes de la familia de C. Declaraciones condicionales son soportadas por if y else, los cuales pueden utilizarse juntos de la siguiente manera:

> let nombre = 'Pedro';
> if (nombre === 'Carolina') {
>   nombre += ' Martinez';
> } else if (name === 'Pedro') {
>   nombre += ' Gonzalez';
> } else {
>   nombre += ' Perez';
> }
< "Pedro Gonzalez"

Por otro lado, JavaScript cuenta con while y do-while. El primero se utiliza más comúnmente en loops básicos, mientras que el segundo, en loops donde se desea que el cuerpo del loop sea ejecutado al menos una vez.

> while (true){
>   //un loop infinito
> }

> let input;
> do{
>   input = get_input();
> } while(inputIsNotValid(input));

El for de JavaScript funciona de la misma manera que en C y Java. Permite controlar la información para el ciclo en una sola línea.

> for (let i =0; i < 5; i++){}
>   //se ejecutará 5 veces
> }

Además, provee otras variantes: for … of (introducido en ES2015):

> for (let value of array){
>   // acción con value (cada elemento del array)
> }

y for … in:

> for (let property in object){
>   //acción con property (cada propiedad del objeto)
> }

Los operadores && y ||, representan el AND y el OR respectivamente, y son utilizados para condiciones lógicas de asignación:

> let name = persona && persona.getName();

Donde si persona es null, no se ejecuta la segunda parte de la condición lógica dado que es dependiente de que la primer parte sea verdadera.

> let name = nombreGuardado || (nombreGuardado = persona.getName());

Donde si nombre guardado no tiene valor se lo setea llamando al método para obtenerlo.

Javascript tiene el operador ternario para expresiones condicionales:

> let permitido = (edad > 18) ? 'sí' : 'no';

Por último, se encuentra switch que es usado para varias posibilidades a la hora de evaluar un número o string:

> switch (accion) {
>   case 'dibujar':
>     dibujar();
>     break;
>   case 'comer':
>     comer();
>     break;
>   default:
>     hacerNada();
> }

Si no se agrega la declaración break, la ejecución seguirá evaluando las siguientes condiciones. Un comportamiento muy raro que sea utilizado de esta manera. En cambio si hay dos condiciones que deben tener un mismo comportamiento, se suele realizar de la siguiente manera:

> switch (a) {
>   case 1:
>   case 2:
>     comer();
>     break;
>   case 3:
>     dibujar();
>   default:
>     hacerNada();
> }

La condición por defecto es opcional. Una variación al switch es donde las condiciones del switch a evaluar sean expresiones, donde la comparación se realiza con el operador ===:

> switch (1 + 3){
>   case 2 + 2:
>     estaOK();
>     break;
>   default():
>     hacerNada();
> }

Objetos

Los objetos en JavaScript pueden ser pensados como una colección simple de pares nombre-valor. Donde la parte del “nombre” es siempre un string y la parte del “valor” puede ser cualquier valor de JavaScript (listados más arriba).

Existen dos formas básicas de generar un objeto vacío:

> let obj = new Object();> let obj = {};

Y se pueden inicializar al momento de la creación de la siguiente manera, como se puede observar la complejidad de la estructura del objeto dependerá de las necesidades que se tenga para representar un modelo:

> let obj = {
>   nombre: 'Pedro',
>   apellido: 'Gonzalez',
>   auto: {
>     color: 'azul',
>     nroPuertas: 5
>   }
> };

Los atributos pueden ser accedidos concatenándolos:

> obj.auto.color;
< "azul"> obj['auto']['nroPuertas'];
< 5

Arrays

Los arrays en JavaScript son un tipo especial de objeto. Trabajan de forma muy similar a un objeto (propiedades numéricas pueden ser accedidas utilizando []). La propiedad length siempre devuelve el índice más alto del array más uno).

Para crear un nuevo array se pueden utilizar dos formas:

> let mascotas = new Array();
> a[0] = 'perro';
> a[1] = 'gato';
> a[2] = 'canario';
> a.length;
< 3
> let mascotas = ['perro', 'gato', 'canario'];
> a.length;
< 3

Para remarcar, es que array.length no necesariamente devuelva la cantidad de elementos del array, por ejemplo:

> let mascotas = ['perro', 'gato', 'canario'];
> mascotas[100] = 'zorro';
> a.length;
< 101

En conclusión, para recordar y no olvidar: length devuelve el índice más alto de un array más uno.

Los arrays cuentan con varios métodos, a continuación detallo algunos:

Array's methods

Funciones

Las funciones en JavaScript son fundamentales para poder entender el lenguaje. Una función básica de ejemplo sería:

> function suma(x, y){
>   let total = x + y;
>   return total;
> }

Una función en JavaScript puede tomar 0 ó más parámetros. El cuerpo de la función puede contener la cantidad de sentencias que desees así como también la declaración de variables que pertenecen al alcance de dicha función. La sentencia return puede ser usada para devolver un valor en cualquier momento, finalizando con la ejecución de la función. Si no se utiliza la sentencia return (o se hace un return sin valor), JavaScript devuelve undefined.

Los parámetros resultan ser nada más que lineamientos para llamar a la función. Se puede llamar a la función sin pasarles los parámetros que espera, en cuyo caso serán seteados como undefined. O incluso, se le puede pasar más parámetros de los esperados:

> sumar();
< NaN
> sumar(1,2,3);
< 3

Las funciones tienen acceso a una variable adicional llamada arguments, la cual es un array de objetos que contiene todos los valores que son pasados como parámetros.

> function sumar(){
>   let suma = 0;
>   for (let i=0, j = arguments.length; i < j; i++){
>     suma += arguments[i];
>   }
>   return suma;
> }

> suma(2, 3, 4, 5);
< 14

De esta manera, hemos creado una función suma que devolverá la suma de todos los parámetros que se le pasen, sin importar la cantidad que sean. Luego, podremos ver cómo escribir esta función de una forma más simplificada utilizando las nuevas funcionalidades proporcionadas por ES6.

Por otro lado, las funciones pueden ser anónimas, es decir, no tengan un nombre definido:

> let suma = function(){
>   let suma = 0;
>   for (let i=0, j = arguments.length; i < j; i++){
>     suma += arguments[i];
>   }
>   return suma;
> }

Como se puede observar, se asignó la función a una variable, esto es, dado que como comenté las funciones son objetos.

Closures

JavaScript ofrece una de las más poderosas abstracciones, pero también que generan potencialmente mayor confusión. Veamos el siguiente ejemplo:

> function generarSumador(a){
>   return function(b){
>     return a + b;
>   };
> }

> let x = generarSumador(5);
> let y = generarSumador(20);

> x(1);
< 6

> x(6);
< 11

> y(7);
< 27

> y(5);
< 25

La función generarSumador devuelve una función, la cual al ser llamada con un parámetro, éste será sumado con el parámetro suministrado inicialmente al llamar a la función original.

En el ejemplo que hemos dado, hay dos copias de generarSumador (x e y), ambas con distintos contextos (a = 5 — cuando hablamos de x y a = 20 — cuando hablamos de y).

En la próxima historia, abordaremos las nuevas funcionalidades y sintaxis provistas por ES6 para luego dar comienzo a la introducción de React.