Tipos de datos en Javascript, lo que no te enseñaron

Tipos de datos en Javascript, lo que no te enseñaron

Introducción

Los tipos de datos en Javascript son un poco diferentes en comparación con otros lenguajes de programación como C o JAVA.

JavaScript es un lenguaje débilmente tipeado, esto quiere decir que no es necesario definir el tipo de dato. Pero tampoco es que no tenga tipos, pues el tipo de dato es definido en tiempo de ejecución por Javascript.

Este comportamiento y especificación de los tipos de datos aplica para cualquier lugar donde se ejecute javascript, ya sea en el navegador, en node.js, mongodb, o cualquier herramienta que use Javascript.

Ahora si analizamos un poco el concepto de tipos, cuando realizas una suma entre números en el mundo real no te importa que tipo es, solo te importa que se puedan sumar, sin importar si es un entero, o decimal. Estos subtipos de números siguen siendo parte del conjunto de números reales, y son utilizados en la vida real.

Javascript nos permite aplicar este pensamiento y con eso nos ahorramos muchas comparaciones de tipos. Habrá ocasiones en que no quede de otra que convertirlos o compararlos, pero entre menos lo hagamos, más sencillo será nuestro código, y por consiguiente más fácil de entender y más rápido de ejecutar.

Se dice que Javascript tienen un sistema de tipos de datos dinámico, esto es porque las variables que se crean pueden recibir cualquier otro tipo de dato en cualquier momento, e ir cambiando de tipo según el valor que guarden. Lo que realmente nos interesa es el valor y que podemos hacer con ese valor, no tanto el tipo.

Más adelante veremos algunas conversiones y comparaciones que pueden confundir y complicar el código, la recomendación es disminuir su uso en la medida de lo posible. Aunque no veremos muy a detalle cada tipo, sí que veremos cosas más importantes, necesarias para usar los tipos de datos en Javascript.

Es útil que se sepa definir variables y constantes, si no sabes como crear una variable o una constante, puedes revisar esta información antes de continuar.

Tipos de datos primitivos

  • Es aquel que no es un objeto
  • No tiene métodos.
  • Son inmutables
  • Tenemos siete tipos datos primitivos en Javascript.
  1. Number, números como; 1, 0, 18500, 89.95124
  2. BigInt, agregado en el 2020, para representar números enteros muy, pero muy grandes, 99999999999999n
  3. String, cadena de caracteres como 'Hola' y "Buenas noches".
  4. Boolean, solo acepta true o false, es decir, si o no.
  5. null, sirve para indicar que algo es nada, su único valor es null.
  6. undefined, sirva para indicar que algo aún no está definido.
  7. Symbol, agregado en el 2015, con EcmaScript 6

Tipos de datos Objetos

Todo lo demás tipos de datos en Javascript son objetos. En la sección Objetos está la explicación.

Number

Si hubiera escrito esto antes del 2020, te diría que solo hay un tipo de dato numérico, lamentablemente (con algunas excepciones) se agregó un nuevo valor numérico llamado BigInt. En esta sección fingiremos que no existe.

Number es un valor flotante de 64 bits, es decir, es un número double en otros lenguajes de programación, básicamente este es el único tipo de dato numérico en javascript, no existen enteros (a excepción de BigInt), decimales, flotantes o doubles. Su especificación está proporcionada por IEEE-754.

La forma de definirlos y usarlos es muy sencilla, simplemente escribes el número, no importa si es entero o decimal, punto flotante o double, como mencionamos antes, todos estos serán de tipo double para javascript. Esta es la forma recomendada de definirlos.

const edad = 33;
const PI = 3.1416;
const descuento = 0.30;
const pesoEnDolares = 0.05; // que tristeza xD!!
edad + descuento; // 33.30, no nos interesa el tipo, solo la suma

Como vemos en la última línea, podemos sumar enteros y decimales sin ningún problema, no nos interesa el tipo, nos interesa la habilidad de sumarlos.

Se sabe que los valores flotantes tienen cierto error de precisión al realizar operaciones aritméticas, la operación más obvia es la división.

0.2 + 0.1; // es igual a 0.30000000000000004

Como vemos no nos da un valor exacto de 0.3, para mitigar esto puedes multiplicar los valores por 100 y luego dividir el resultado entre 100.

(0.2 * 100 + 0.1 * 100)/100; // es igual a 0.3

NaN

Existe un número especial llamado NaN. Este valor es el error de una operación con números.

25 * undefined; // es igual a NaN
25 * {}; // es igual a NaN

Todo operación que tenga NaN como uno de sus operandos, dará como resultado NaN.

25 * NaN; // es igua a NaN
25 + NaN; // es igual a NaN

NaN ni siquiera es igual a NaN

NaN === NaN; // es igual a false
NaN == NaN; // es igual a false

Para mitigar este extraño comportamiento, se recomienda usar el método Number.isNaN(), también existe una función global llamada isNaN(), pero debido a la funcionalidad implícita de Javascript en querer convertir entre tipos automáticamente, nos puede dar resultados muy extraños. Así que como recomendación siempre usa Number.isNaN(). Esta última función revisa si el tipo es Number e igual a NaN, cualquier otro valor nos regresara false.

// Usando funcion global
isNaN('jaime'); // true --> Esto esta mal, deberia seguir regresando false
// lo que realmente hace es una conversion automatica
const result = Number('jaime'); // NaN
isNaN(result); // true
Number.isNaN('343434'); // false
Number.isNaN('jaime'); //false
Number.isNaN(1 + undefined); // true

Podemos ver en el primer ejemplo que la función global regresa true cuando debería ser false, esto es por la conversión automática de tipos que hace javascript.

Función Number(valor)

Puedes convertir un valor a número usando la función Number(valor), solo ten cuidado porque esta función puede regresar 0 para muchos valores, así como también NaN.

Number('5'); // 5
Number(''); // 0
Number(undefined); // NaN
Number(null); // 0
Number('8778jaime'); // NaN
Number(false); // 0
Number(true); // 1;
Number({}); // NaN
Number([]); // 0

Number.parseInt y Number.parseFloat

Por último tenemos las funciones Number.parseInt(valor) y Number.parseFloat(valor), si el valor no es un string, entonces lo convierten a string utilizando el método .toString() antes de convertirlo a Number.

Number.parseInt(''); // NaN
Number.parseInt('78.5jaime'); // 78
Number.parseInt(78.58977); // 78
Number.parseFloat(''); // NaN
Number.parseFloat('78.5jaime'); // 78.5

BigInt

Este tipo de valor es nuevo, agregado al lenguaje en el 2020, se utiliza para representar valores enteros muy grandes, especialmente para cálculos matemáticos con cantidades muy largas.

Para usar este tipo de dato al número se le agrega la letra n al final. Esta es la forma recomendada.

const enteroMuyGrante = 999999999999999999999999999999999999999999999999999n;
const otroEnteroMuyGrande = 55555555555555555555555555555555555555555555555n;
const result = enteroMuyGrande * otroEnteroMuyGrande; //

Se pueden realizar operaciones entre el tipo Number y el BigInt, pero debido a que la conversión entre tipos puede provocar perdida de precisión, se recomienda solo usar valores BigInt cuando se estén ocupando valores mayores a 253 y solo realizar operaciones con valores del mismo tipo BigInt.

Otra mala noticia sobre BigInt es que no puedes utilizar sobre un valor BigInt los métodos del objeto Math, por ejemplo no puedes utilizat Math.sqrt().

Math.sqrt(99); // 9.9498743710662
Math.sqrt(99999999999999999n); // Uncaught TypeError: Cannot convert a BigInt value to a number

Como recomendación final, no te compliques la vida, no uses BigInt a menos que el problema que quieras solucionar necesite forzosamente números enteros muy grandes.

Función BigInt(valor)

Puedes convertir y crear un BigInt con la función BigInt(valor)

BigInt('9999999999'); // 9999999999n

Boolean

Boolean es un tipo de dato lógico, el cual es representado únicamente por dos valores true y false. Muy útil para tomar decisiones en nuestros programas. Nombrado así por el matemático George Boole.

Se pueden definir valores tipo Booleanos de la siguiente manera. Esta es la forma recomendada

const tieneHijos = true;
const esEstudiante = false;

Se usan mucho en operaciones lógicas, por ejemplo:

if (tieneHijos) {
  // Si "si" tiene hijos, preguntar cuantos hijos tiene
} else {
  // si "no"
}
if (esEstudiante) {
  // Si "si" es estuduante, aplicar descuento
} else {
  // si "no"
}

Función Boolean(valor)

Los Booleanos también tiene una función Boolean(valor), es adecuado para crear booleanos y convertir explicitamente un valor a Booleano. Más detalles abajo en la sección Valores que pueden generar Booleanos.

null

Este tipo de dato en Javascript es muy fácil, es un dato que significa que no es nada en comparación con los demás tipos de datos. En otros lenguajes null hace referencia a un apuntador o una referencia de memoria que está vacía, pero en javascript es diferente, simplemente es un valor que significa “nada”.

const nada = null;

undefined

Este tipo de dato representa algo que aún no está definido. Es el valor automático de las variables, parámetros y propiedades de objetos que no se les ha definido un valor.

let nombre; // undefined
let edad; // undefined
nombre === undefined; // true
edad === undefined; // true

Symbol

Este tipo de dato se utiliza principalmente para garantizar que su valor sea único e inmutable. Se puede usar como propiedades de objetos para reducir accesos a esas propiedades y con eso evitar modificaciones indeseadas. Cuando veamos más a detalle a los objetos veremos en la práctica su uso.

Para crear este tipo de dato primitivo se usa la función Symbol(). Es la única forma de crear un symbol y es la recomendada.

const nombre = Symbol('Jaime');
const nombreIgual = Symbol('Jaime');
nombre === nombreIgual; // false

String o cadena de caracteres

Los string nos permiten guardar información en forma de texto. Son una secuencia de uno o más caracteres de 16 bits, UTF-16. No existe el tipo `char` como en otros lenguajes, si deseas un carácter, pues simplemente creas un string con un solo carácter. ¿Sencillo no?

Tenemos tres formas para crear un string.

En realidad se puede también crear strings con la función String(valor) o en forma de constructor new String(valor), pero no nos compliquemos la existencia y ocupes solo las tres de abajo por simplicidad:

  1. Con comillas simples, ''. Recomendada si no necesitas evaluar expresiones dentro.
  2. Comillas dobles, ""
  3. backticks, `` . Recomendada si necesitas evaluar expresiones dentro.
const nombre = 'Jaime';
const apellidoPaterno = "Cervantes";
const apellidoMaterno = `Velasco`;

La tercera forma de definir Strings, nos permite usar expresiones dentro de la cadena de caracteres, por lo que es muy útil. Por ejemplo:

const nombre = 'Jaime';
const apellidoPaterno = "Cervantes";
const apellidoMaterno = `Velasco`;
const nombreCompleto = `${nombre} ${apellidoPaterno} ${apellidoMaterno}`; // Jaime Cervantes Velasco

Los strings son inmutables, cuando modificas o extiendes un string realmente se genera uno nuevo.

Dos cadenas de caracteres pueden ser iguales

Puedes comprobar que dos cadenas de caracteres son iguales con ===.

"jaime" === "jaime"; // true
'jaime' === "jaime"; // true
`jaime` === 'jaime'; // true

Función String(valor)

Para convertir un valor a cadena de caracteres puedes usar la función String(valor).

String(1); // '1'
String(null); // 'null'
String(undefined); // 'undefined'

String.length

La propiedad length de un string se usa para saber la longitud de una cadena de caracteres, verás con el tiempo que esto es útil cuando trabajas con cadenas de caracteres.

const nombre = 'Jaime Cervantes Velasco';
nombre.length // 23

Objetos

Todo lo demás tipos de datos son objetos, una función es un objeto. Un objeto es una colección de pares nombre: valor, parecido a los “arrays asociativos” de PHP. Estos pares de nombre/valor se les llama propiedades, una propiedad es como una variable y puede contener cualquier tipo de valor.

Algunos objetos en Javascript:

  • Object
  • Function
  • Array
  • Date
  • RegExp
  • Error

Objetos envolventes de primitivos

Incluso los tipos de datos primitivos String, Boolean, Number, BigInt y Symbol tiene su correspondiente representación en Objeto, llamados objetos envolventes. De hecho Javascript implícitamente convierte estos datos primitivos a objetos para poder ocupar métodos y propiedades útiles. Por ejemplo la propiedad length de una cadena de caracteres.

¿Cómo pensabas obtener la longitud de una cadena de caracteres? Entendiendo que una cadena de caracteres es un dato primitivo y no tiene métodos

'Jaime Cervantes Velasco'.length; // 23, el pri
// A groso modo, lo que sucede implictamente al querer obtener lenght:
const nuevoString = new String('Jaime Cervantes Velasco');
nuevoString.length; // 23
(5).toFixed(2); // 5.00
// Igual a groso modo pasa algo asi:
const numero = new Number(5);
numero.toFixed(2); // 5.00
false.toString(); // 'false'
// Igual a groso modo pasa algo asi:
const booleano = new Boolean(false);
numero.toString(2); // false
// Estos dos tipos de datos primitivos no permiten el uso de new
(9n).toLocaleString(); // '9'
Symbol('Hi').toString(); // 'Symbol(Hi)'

Aunque los tipos de datos BigInt y Symbol no permiten el uso de new no quiere decir que por detrás no se creen objetos, está claro que javascript los envuelve en un objeto porque nos permite utilizar los métodos del ejemplo, toLocaleString y toString.

¿Cómo crear un objeto?

Para crear un objeto no necesitas crear una clase, tú simplemente creas el objeto y lo empiezas a utilizar. La forma más fácil de crear un objeto es usando la definición que llaman objeto literal, donde simplemente abres y cierras llaves {}. Esta es la forma recomendada.

const vacio = {};
const persona = {
  nombre: 'Jaime',
  apellidos: 'Cervantes Velasco',
  edad: 33,
  getNombre: function () {
    return `${this.nombre} ${this.apellidos}`;
  },
  hablar: function () {
    return `Hola, soy ${this.getNombre()}, tengo ${this.edad} años.`;
  }
};
const frase = persona.hablar();
console.log(frase); // 'Hola soy Jaime Cervantes Velasco, tengo 33 años.'

En el ejemplo tenemos un objeto persona, tiene varias propiedades de diferentes tipos, su nombre es un String, su edad es un NumbergetNombre y hablar son de tipo Function, las funciones que son miembros de un objeto se les llama métodos.

Más abajo hay ejemplos de objetos.

Accediendo a propiedades con corchetes, ['key']

En el ejemplo anterior vimos como acceder al método hablar(), este método sigue siendo una propiedad y es de tipo function. Pero existe otra notación para acceder a las propiedades, usando corchetes. Muy parecido a como se acceden a los elementos de un array.

persona.edad; // regresa 33
persona['edad']; // regresa 33
// obtenemos la funciónn con corcheteds y la invocamos con parentesis
const frase = persona['hablar']();
console.log(frase); // 'Hola soy Jaime Cervantes Velasco, tengo 29 años.'

¿Cómo crear un objeto function?

function imprimirPersona(persona) {
  console.log(persona.nombre);
  console.log(persona.edad);
}

Como la función imprimirPersona es un objeto, podemos agregarle propiedades sin ningún problema.

imprimirPersona.miPropiedad = 'Mi propiedad de una funcion';
console.log(imprimierPersona.miPropiedad); // 'Mi propiedad de una funcion

¿Cómo crear un objeto Array?

La forma recomendada es simplemente crearlos usando corchetes [].

const vacio = [];
const numeros = [1, 2, 3, 4, 5, 6];
const animales = ['perro', 'gato', 'caballo'];
const conObjetos = [
  		{
          nombre: 'Jaime',
          edad: 33
        },
        function imprimierPersona(persona) {
          console.log(persona);
        }
	];

Como un array es un objeto, igual que una función, también le puedes agregar propiedades.

const numeros = [1, 2, 3, 4, 5, 6];
numeros.miPropiedad = 'Mi propiedad de un arreglo';
console.log(numeros.miPropiedad); // 'Mi propiedad de un arreglo

¿Cómo crear un objeto Date?

const fecha = new Date();
console.log(fecha); // Fri May 28 2021 10:46:27 GMT-0500 (hora de verano central)

Como te podrás imaginar, al objeto date también le podemos agregar propiedades porque es un objeto

De momento espero que esto te dé una idea de la importancia de los objetos en Javascript y por eso se merece una explicación más extensa en una futura publicación.

Valores que pueden generar un boleano false

Existen valores verdaderos y valores falsos, que aplicados en una condición se comportan como Booleanos. Esto es debido a que Javascript hace una conversión de tipos implícita, que nos puede sorprender si no tenemos cuidado, por eso aquí te dejo la lista de valores que pueden regresar un Boleano false.

  • false
  • null
  • undefined
  • 0
  • 0n
  • NaN
  • ”, “”, “ (cadena de caracteres vacía)

Todos los demás valores, incluyendo los objetos, en una condición o pasados a la función Boolean(valor) regresan el Boleano true. Un string con un espacio " ", regresa true, y un string con valor "false" también regresa true.

Función Boolean(valor)

Existe una función global para crear valores Booleanos, Boolean(valor). Donde si valor es uno verdadero, regresa true, de lo contrario, regresa false.

Boolean(false); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(0); // false
Boolean(0n); // false
Boolean(NaN); // false
Boolean(''); // false
// Todos los demas valores van a regresar true
Boolean(' '); // true
Boolean('false'); // true
Boolean('jaime cervantes'); // true
Boolean({}); // true
Boolean([]); // true;

En condiciones lógicas

Las condiciones lógicas en javascript verifican sin una expresión regresa true o false, ejecutan un conversión implicita.

if (false) {
} else {
  console.log(false);
}
if (null) {
} else {
  console.log(false);
}
if (undefined) {
} else {
  console.log(false);
}
if (0) {
} else {
  console.log(false);
}
if (0n) {
} else {
  console.log(false);
}
if (NaN) {
} else {
  console.log(false);
}
if ('') {
} else {
  console.log(false);
}
if (' ') {
  console.log(true);
}
if ('false') {
  console.log(true);
}
if ({}) {
  console.log(true);
}
if ([]) {
  console.log(true);
}

Verificar tipos con el operador typeof

Este operador nos permite identificar el tipo de dato con el que estamos trabajando, útil en caso de que no sepamos de donde viene la información, o queremos tratar los datos con base en su tipo. El operador regresa el nombre en minúsculas del tipo de dato.

typeof 5; // 'number
typeof NaN; // 'number'
typeof 99999999999999999999999999999999999999999999999n; // 'bigint'
typeof true; // 'boolean'
typeof null; // 'object', esto es un error que existe desde la primera versión de JS
typeof undefined; // 'undefined'
typeof Symbol('simbolo'); // 'symbol'
typeof 'cadena de caracteres'; // 'string'
typeof {}; // 'object'
typeof []; // 'object
typeof new Date(); // 'object'
typeof new String('Jaime'); // Object
typeof function() {} // 'function'
typeof class A {} // 'function'

Existe un error conocido en javascript, cuando se ejecuta el operador typeof sobre null, este regresa ‘object'. Este error existe desde la primera versión de Javascript, cosas malas pasan cuando escribes código a las carreras.

Si todos los demás valores que no son primitivos son objetos, cuando usamos el operador typeof sobre ellos, ¿Qué tipo de datos nos indicara que son?, exacto, 'object'. Tal como en el ejemplo de arriba cuando pasamos un objeto literal, un array vacío, un objeto tipo fecha y cuando ocupamos los objetos envolventes de primitivos como new String('Jaime').

Existe una excepción en typeof, cuando el valor es una función o una clase, regresara function. Recuerda, una función en javascript, también es un objeto.

Verificar tipos con el método Object.prototype.toString

Como vimos, es algo confuso utilizar el operador typeof, yo prefiero utilizar un método más seguro, es algo raro, pero como todo en Javascript es un objeto y el objeto base del cual parten todos los tipos de datos en javascript es Object, con excepción de los datos primitivos ( Aunque estos a su vez tienen su versión objeto), entonces podemos hacer algo como lo siguiente.

Object.prototype.toString.call(5); // '[object Number]'
Object.prototype.toString.call(NaN); // '[object Number]'
Object.prototype.toString.call(99999999999999999999999999999999999999999999999n); // '[object BigInt]'
Object.prototype.toString.call(true); // '[object Boolean]'
Object.prototype.toString.call(null); // '[object Null]',
Object.prototype.toString.call(undefined); // '[object Undefined]'
Object.prototype.toString.call(Symbol('simbolo')); // '[object Symbol]'
Object.prototype.toString.call('cadena de caracteres'); // '[object String]'
Object.prototype.toString.call({}); // '[object Object]'
Object.prototype.toString.call([]); // '[object Array]'
Object.prototype.toString.call(new Date()); // '[object Date]'
Object.prototype.toString.call(new String('Jaime')); // '[object String]'

A partir de este método, se puede crear una función que verifique el tipo de dato.

function getTypeOf(valor) {
  const texto = Object.prototype.toString.call(valor); // '[object Number]', '[object Boolean]', etc
  const sinPalabraObject = texto.slice(8, -1); // Number, Boolean, Null, Undefined, Symbol, String, etc.
  const tipo = sinPalabraObject.toLowerCase(); // number, boolean, null, undefined, symbol, string, etc.
  return tipo;
}
getTypeOf(1); // 'number'
getTypeOf(new Date()); // date
getTypeOf([]); // array

La propiedad especial prototype es muy importante en Javascript, si has leído más de una de mis publicaciones, te habrás percatado que he mencionado que Javascript es un lenguaje de programación multiparadigma y orientado a objetos basado en prototipos, en otra publicación explicaremos a detalle la funcionalidad de prototipos en Javascript.

Conclusión

Todo en Javascript es un objeto, con la excepción de los tipos primitivos, pero como ya vimos, al querer usarlos como objetos invocando algún método, es ahí donde javascript los envuelve (en la practica todo es un objeto).

Cualquier lenguaje de programación tiene buenas cosas que podemos aprovechar, pero también puede tener cosas extrañas y confusas (comparando con la mayoria de los lenguajes de programación modernos), como el operador typeof y el método Object.prototype.toString, el valor NaN, los valores que pueden generar un boleano false, y muchas otras cosas que no vimos por razones de simplicidad. Queda del lado del programador tomar las mejores partes y eliminar o mitigar las que no nos ayudan a comunicarnos mejor con nuestro equipo de trabajo, siempre con un enfoque a simplificar las cosas.

En el caso de la creación de algún tipo de dato en Javascript, la manera más simple y fácil de usar, es simplemente creando los valores que vamos a utilizar. Tal como se recomendó en secciones anteriores, a esta forma de crearlos se les llama literales.

const numero = 9999.54;
const enteroGrande = 99999999999999n;
const boleano = true;
const nulo = null;
let noDefinido;
const simbolo = Symbol('identificador');
const cadenaDeCaracteres = 'Jaime Cervantes Velasco';
const cadenaDeCaractresConExpresion = `El numero ${numero} es "Number"`;
const objeto = {};
const arreglo = [];

Existen aún más detalles que cubrir sobre los tipos de datos en javascript, pero sobre la marcha iremos viendo más temas necesarios, de momento me parece que esta información es suficiente.

Paradigma de programación en JavaScript

Paradigma de programación en JavaScript

¿Qué es un paradigma de programación?

El modelo o mapa de como vemos el mundo real, eso es un paradigma, es una manera de ver y hacer las cosas. Siguiendo esta lógica, un paradigma de programación no es más que una forma de ver y crear código de programación.

Los paradigmas son poderosos porque crean los cristales o las lentes a través de los cuales vemos el mundo.

Stephen R. Covey

Existe tres principales paradigmas de programación utilizados en la actualidad y que en JavaScript siempre han existido desde su primera versión.

  • Paradigma de programación Estructurada
  • Orientada a objetos
  • Funcional

Ahora bien, si nos apegamos a la frase de Stephen R. Covey, sobre que los paradigmas crean las lentes a través de los cuales vemos el mundo. Imaginemos que los tres paradigmas anteriores, cada uno, son un par de lentes que nos ponemos a la hora de programar y que podemos cambiar esos lentes según nuestras necesidades visuales (de programación o solución de problemas).

Paradigma de programación estructurada

Origen

En el año 1954 apareció el lenguaje de programación FORTRAN, luego en 1958 ALGOL y en 1959 COBOL. Tiempo después Edsger Wybe Dijkstra en 1968 descubre el paradigma de programación estructurada, decimos que “descubre” porque en realidad no lo inventaron. Aunque este paradigma de programación se formalizó tiempo después de la aparición de estos lenguajes de programación, era posible hacer programación estructurada en ellos.

Dijkstra reconoció que la programación era difícil, y los programadores no lo hacemos muy bien, de lo cual estoy totalmente de acuerdo. Un programa de cualquier complejidad tiene muchos detalles para el cerebro humano. De hecho la neurociencia nos indica que la mente enfocada solo puede trabajar con cuatro datos a la vez, cuanto mucho. Por esta razón, si se descuida un pequeño detalle creamos programas que parecen funcionar bien, pero que fallan en formas que uno nunca se imagina.

Pruebas

La solución de Dijkstra fue utilizar pruebas, solo que estas pruebas usaban mucho las matemáticas, lo cual era bastante difícil de implementar. Durante su investigación encontró que ciertos usos de GOTO hacían difícil descomponer un programa grande en piezas más pequeñas, no se podía aplicar “divide y vencerás”, necesario para crear pruebas razonables.

Secuencia, selección e iteración

Patrones de la programación estructurada
Patrones de la programación estructurada

Böhm y Jacopini probaron dos años antes que todos los programas pueden ser construidos por solo tres estructuras: secuencia, selección e iteración. Dijkstra ya conocía estas tres estructuras y descubrió que estas mismas son las necesarias para hacer que cualquier parte del programa sea probado. Es aquí donde se formaliza el paradigma de programación estructurada.

Edsger nos indica que es una mala práctica usar GOTO y sugiere usar mejor las siguientes estructuras de control:

  • If, then, else (selección)
  • do, while, until (iteración o repetición)

Descomposición de pequeñas unidades fáciles de probar

Hoy en día la mayoría de los lenguajes de programación utilizan la programación estructurada, JavaScript utiliza este tipo de programación. El ejemplo más sencillo son las condiciones if.

const EDAD_MINIMA = 18;
if (edad >= EDAD_MINIMA) {
	// hacer algo porque es mayor que 18
} else {
	// Hacer otra cosa en caso contraio
}

Y si lo aislamos en una función, tenemos una descomposición de funcionalidades o unidades que pueden ser probadas con mayor facilidad. En la siguiente sección veremos la importancia de tener una unidad completamente probable

const EDAD_MINIMA = 18;
function esMayorEdad(edad) {
  if (edad >= EDAD_MINIMA) {
     return true;
  } else {
     return false;
  }
}

Con esta condición tenemos un control directo de lo que puede suceder si la edad es mayor o igual a 18, y también en el caso de que la edad sea menor.

Tampoco seamos tan estrictos, seguro que la sentencia GOTO tiene sus méritos y tal vez era muy eficiente en algunos casos, pero pienso que para evitar malos usos que probablemente causaban desastres, Dijkstra recomendó dejar de usar GOTO.

Pruebas, divide y vencerás

La programación estructurada nos permite aplicar la filosofía de “divide y vencerás”, porque nos permite crear pruebas de pequeñas unidades, hasta tener todo el programa cubierto. La solución matemática de Wybe Dijkstra nunca se construyo, precisamente por su dificulta de implementación. Y lamentablemente en nuestros días aún existen programadores que no creen que las pruebas formales sirven para crear software de alta calidad.

Y digo crear, porque verificar el correcto funcionamiento después de crear, no es más que una simple medición y se pierde la oportunidad de reducir tiempo y dinero.

Método científico, experimentos e impirismo

Método científico, experimentos y pruebas
Método científico, experimentos y pruebas, por ThisIsEngineering Raeng en unsplash

La buena noticia es que el método matemático no es la única forma de verificar nuestro código, también tenemos el método científico. El cual no puede probar que las cosas sean absolutamente correctas desde el punto de vista matemático. Lo que si se puede hacer es crear experimentos y obtener resultados suficientes para verificar que nuestras teorías funcionan. Dado que nuestras teorías funcionan sobre estos experimentos, entonces concluimos que son lo suficientemente “correctas” para nuestros propósitos de validación.

Si la teoría es fácilmente validada como falsa, entonces es momento de modificarla o cambiarla. Así es el método científico y así es como actualmente se hacen las pruebas:

Primero establecemos nuestra teoría (escribimos la prueba), después construimos los experimentos (escribimos código de producción) y finalmente verificamos (corremos la prueba) y repetimos este ciclo hasta tener evidencia suficiente.

¿Esto no te suena a una práctica ágil actual llamada TDD?

Las pruebas muestran la presencia, no la ausencia, de defectos

Edsger Wybe Dijkstra dijo “Las pruebas muestran la presencia, no la ausencia, de defectos”. Ósea, un programa puede ser probado incorrecto mediante pruebas, pero no puede ser probado correcto. Todo lo que podemos hacer con nuestras pruebas es validar que nuestro programa funciona lo suficiente bien para nuestros objetivos. Pero nunca podemos estar cien por ciento seguros de la ausencia de defectos.

Si nunca podemos estar cien por ciento seguros que nuestro código es correcto, aun con sus pruebas, ¿Qué nos hace pensar que sin pruebas entregamos un programa con la suficiente validez para afirmar que no se comete conscientemente alguna negligencia?

El control directo

La programación estructurada nos enseña sobre el control directo, es decir, controlar el flujo secuencial de las operaciones a través de las estructuras de control, sin descuidar la recomendación de Wybe Dijkstra sobre GOTO. Esta recomendación también es aplicable a sentencias que cambian drásticamente el flujo normal de las operaciones, por ejemplo un break dentro de un ciclo for anidado, ese break rompe el control directo porque complica abruptamente el entendimiento del algoritmo. Tampoco seamos puristas, seguro habrá casos que necesites de estas sentencias, pero de primera instancia, trata de evitarlas.

En conclusión, el paradigma de programación estructurada nos enseña:

El control directo, claro y explicito de lo que hace un pedazo de código

Paradigma de programación orientada a objetos

¿Surgido de funciones?

El paradigma de programación orientada a objetos se descubrió en 1966 por Ole Johan Dahl y Kristen Nygaard cuando desarrollaban simula 67. Dos años antes de la programación estructurada y su adopción por la comunidad de programadores fue tiempo después. En 1967 se lanzó Simula 67, Simula fue el primer lenguaje de programación orientado a objetos,  básicamente le agregó clases y objetos a ALGOL, inicialmente Simula iba a ser una especie de extensión de ALGOL.

Durante el desarrollo de Simula, se observó que la ejecución de una función de ALGOL necesita de ciertos datos, los cuales se podían mover a una estructura de árbol. Esta estructura de árbol es un Heap.

El heap permitía declarar variables locales de la función que pueden existir incluso después de que la función regrese un valor. ¿Esto último no les suena a lo que es closure en JavaScript?

La función padre se convierte en un constructor, las variables locales se convierten en las propiedades y las funciones hijas (anidadas) en sus métodos. De hecho este patrón es aún muy utilizado por Javacript para crear módulos y usar herencia funcional. También esto describe como funcionan las clases en Javascript, por detrás son funciones.

Esparcimiento del polimorfismo

Todo esto ayudó mucho a que se implementará el polimorfismo en los lenguajes de programación orientados a objetos. Ole Johan Dahl y Kristen Nygaard inventaron la notación:

objeto.funcion(parametro)

La ejecución de funcion que pertenece a objeto, pero más importante, le pasamos un mensaje a través de parámetros desde la primera parte del código a la segunda parte localizada en un objeto diferente.

La biología molecular y el paso de mensajes

Paso de mensajes entre células
Paso de mensajes entre células, Imagen modificada de “Signaling molecules and cellular receptors: Figure 1,” por OpenStax College, Biology (CC BY 3.0).

Tiempo después se creó Smalltalk, un lenguaje de programación orientado a objetos mucho más sofisticado y moderno, a cargo de este proyecto estaba Alan Kay. A esta persona se le atribuye la definición formal de la programación orientada a objetos.

La principal influencia en la programación orientada objetos de Alan Kay fue la biología, él es licenciado en biología. Aunque es obvio que fue influenciado por Simula, la influencia de LISP (lenguaje de programación funcional) no es tan obvia.

Desde un inicio él pensaba en los objetos como células interconectadas en una red, de la cual se podrían comunicar mediante mensajes.

Alan Kay comentó lo siguiente:

Lamento que hace tiempo haya acuñado el término Objetos para la programación porque hizo que las personas se enfocaran en la parte menos importante. La gran idea es “Envío de mensajes“.

Alan Kay

Mi Ingles es malo así que pueden revisar el texto original aquí.

Mejor composición de objetos sobre herencia de clases

Smalltalk permitió también la creación de Self, creado en Xerox Parc y luego migrado a Sun Microsystems Labs. Se dice que Self es una evolución de smalltalk.

En este lenguaje de programación se introdujo la idea de prototipos, eliminando la utilización de clases para crear objetos, este lenguaje utiliza los objetos mismos para permitir que un objeto reutilice las funcionalidades de otro.

Self es un lenguaje rápido y en general se le conoce por su gran rendimiento, self hizo un buen trabajo en sistemas de recolección de basura y también utilizaba una maquina virtual para administrar su ejecución y memoria.

La máquina virtual Java HotSpot se pudo crear gracias a Self. Lars Bak uno de los últimos contribuidores de Self, se encargó de la creación del motor de Javascript V8. Debido a la influencia de Self tenemos hoy en día el motor de JavaScript V8, el cual lo utiliza node.js, mongoDB y Google Chrome internamente.

Self seguía una de las recomendaciones del libro Design Patterns: Elements of Reusable Object-Oriented Software, mucho antes de que saliera publica esta recomendación:

Mejor composición de objetos a herencia de clases.

Design Patterns: Elements of Reusable Object-Oriented Software

Ejemplo

Actualmente Javascript utiliza prototipos para la reutilización de código, se podría decir que usa composición, en lugar de herencia. Veamos un ejemplo:

const persona = {
    saludar () {
      return 'Hola'
    }
};
// se crea un programador con el prototipo igual al objeto persona
const programador = Object.create(persona);
programador.programar  = () => 'if true';
const saludo = programador.saludar();
const codigo = programador.programar();
console.log(saludo); // 'Hola'
console.log(codigo); // 'if true'

El objeto programador se basa en el prototipo de `persona`. No hereda, solo reutiliza directamente la funcionalidad de `persona`.

JavaScript surge poquito después de JAVA, en el mismo año. JavaScript tomo influencias de Self, y a su vez Self fue influido por smalltalk.

La sintaxis de clases actuales en Javascript no es más que una fachada, bueno un poco más exacto son una evolución de las funciones constructoras, pero internamente, su base, son las funciones y los prototipos.

La comunicación con el paso de mensajes

Como ya vimos, el origen de la programación orientada objetos tiene su origen en las funciones, y la idea principal siempre ha sido la comunicación entre objetos a través del paso de mensajes. Podemos decir que La programación orientada a objetos nos habla del paso de mensajes como pieza clave para la comunicación de objetos, por ejemplo, cuando un método de un objeto invoca el método de otro.

objeto.funcion(parametro);

En conclusión, el paradigma de programación orientado a objetos, nos enseña:

El paso de mensajes para la comunicación entre objetos

Paradigma de programación funcional

Cálculo lambda

Imagen tomada de https://www.slideshare.net/FASTPresentations/introduction-to-lambda-calculus-using-smalltalk-by-facundo-javier-gelatti

La programación funcional es el paradigma de programación más viejo, de hecho su descubrimiento fue mucho antes de la programación para computadoras, fue descubierto en 1936 por Alonzo Church, cuando invento el cálculo lambda, base del mismo problema a resolver de su alumno Alan Turing.

Como dato curioso el símbolo del cálculo lambda es:

λ

El primer lenguaje basado en programación funcional fue LISP, creado por John McCarthy. Como vimos anteriormente, Alan Kay también tomo influencias de LISP para crear Smalltalk, con esto podemos comprobar que los paradigmas de programación pueden estar unidos y su relación entre ellos surge de la idea de como resolver problemas de manera más eficiente, no están peleados, ni separados, buscan el mismo objetivo y de alguna manera evolucionan en conjunto.

La base del cálculo lambda es la inmutabilidad, más adelante revisaremos este concepto.

Similitudes con los paradigmas OO y estructurada

Como vimos en el paradigma de programación estructurada, podemos descomponer nuestros programas en unidades más pequeñas llamadas procedimientos o funciones. Dado que la programación funcional es el paradigma más viejo, podemos decir que el paradigma de programación estructurada también se apoya en la programación funcional.

Ahora bien, si analizamos un poco la notación objeto.funcion(x) que vimos en la sección del paradigma de programación orientada a objetos, no es muy diferente de funcion(objeto, parametros), estas dos lineas de abajo son iguales, la idea fundamental es el paso de mensajes, como nos indica Alan Kay.

objeto.funcion(parametro);
funcion(objeto, parametro);

Smalltalk usa el “El Modelo Actor”, el cual dice que los actores se comunican entre ellos a través de mensajes.

Por otro lado tenemos a LISP, el cual tiene un “Modelo despachador de funciones” que actualmente le llamamos lenguaje funcional, estos modelos son idénticos, porque las funciones y métodos lo que hacen es enviar mensajes entre actores.

Un ejemplo de esto es cuando una función llama a otra función o cuando se invoca el método de un objeto, lo que sucede es que existen actores y se comunican entre ellos a través de mensajes.

De nuevo el paso de mensajes

Entonces podemos recalcar que la idea principal de la POO es el envío de mensajes y que no es muy diferente de la programación funcional, de hecho según lo que ya establecimos en las secciones anteriores, POO nació de una base funcional. Y aquí es importante remarcar lo que dijo Alan Kay Co-creador de SmallTalk:

Lamento que hace tiempo haya acuñado el término Objetos para la programación porque hizo que las personas se enfocaran en la parte menos importante. La gran idea es “Envío de mensajes“.

Alan Kay

A partir de los modelos de comunicación entre mensajes de Smalltalk y LISP, se creó Scheme.

Scheme tiene la bondad de usar Tail recursion y closure para obtener un lenguaje funcional, closure permite tener acceso a las variables de una función externa aun cuando esta ya haya regresado algún valor. Esto es el mismo principio que origino a la programación orientada a objetos a cargo de Ole Johan Dahl y Kristen Nygaard. ¿Recuerdan la pregunta de closure en Javascript?

Javascript usa mucho closure en su paradigma de programación funcional. JavaScript tomó influencias de Scheme, a su vez Scheme fue influido por LISP.

Sin efectos colaterales

La base del cálculo lambda es la inmutabilidad, por consiguiente, la inmutabilidad también es la base del paradigma de programación funcional.

Nosotros como programadores funcionales debemos seguir este principio y limitar las mutaciones de objetos lo más que se pueda para evitar efectos colaterales.

Repito, la idea principal de la programación funcional es la inmutabilidad, la cual te permite crear tus programas con menos errores al no producir efectos colaterales difíciles de controlar.

Como ejemplo, supongamos que tenemos un objeto persona y queremos “cambiar” el nombre, lo más obvio seria modificar la propiedad nombre directamente, pero que pasa si ese objeto es utilizado en otra parte y se espera que nombre sea “Jaime”, es por eso que en lugar de cambiar la propiedad nombre, solo creamos un nuevo objeto persona con diferente nombre, sin modificar el original.

function cambiarNombre(nombre, persona) {
  return {
    nombre: nombre,
    edad: persona.edad
  }
}
const jaime = { nombre: 'Jaime', edad: 30 };
const juan = cambiarNombre('Juan', jaime);
console.log(jaime); // { nombre: 'Jaime', edad: 30 }
console.log(juan); // { nombre: 'Juan', edad: 30 }

Aquí encontrarás más detalles sobre los principios fundamentales de programación funcional:

Introducción a programación funcional

Por último, el paradigma de programación funcional nos enseña:

Sin efectos colaterales, para una expresión clara y menos propensa a errores

Conclusión

Es importante destacar que si los tres paradigmas de programación fueron implementados entre los años 1958 con LISP (programación funcional) y Simula (programación Orientado a objetos) en 1966, y el descubrimiento del paradigma de programación estructurado en 1968, en solo un lapso de 10 años existió la innovación de los paradigmas de programación, y realmente nuevos paradigmas no han surgido, al menos que no sean basados en estos tres principales.

Ahora mismo ya han pasado más de 60 años, lo que nos dice de la importancia y la firmeza que tienen a pesar del tiempo transcurrido.

  • Paradigma de programación funcional (1958, ya implementado en un lenguaje de programación para computadoras, recordemos que fue descubierto en 1936)
  • Orientado objetos (1966)
  • Estructurada (1968)

La implementación de estos tres paradigmas desde el inicio en el interior de JavaScript han hecho que este lenguaje de programación tenga el éxito global de hoy en día. Y nos comprueba lo valioso de combinar los paradigmas al escribir nuestros programas.

JavaScript es en lenguaje de programación multiparadigma, por lo que se puede combinar los paradigmas para crear un código mucha más eficiente y expresivo usando:

  • El control directo, claro y explicito de lo que hace un pedazo de código
  • El paso de mensajes para la comunicación entre objetos
  • Sin efectos colaterales, para una expresión clara y menos propensa a errores

Referencias

https://en.wikipedia.org/wiki/Structured_programming

https://en.wikipedia.org/wiki/Structured_program_theorem

http://wiki.c2.com/?AlanKayOnMessaging

https://www.slideshare.net/FASTPresentations/introduction-to-lambda-calculus-using-smalltalk-by-facundo-javier-gelatti

Libro Clean architecture

Variables en Javascript y constantes

Variables en Javascript y constantes

Variables

Uno de los elementos más usados son las variables en Javascript. Podemos describir a las variables en Javascript como cajas para guardar información, las cuales etiquetamos para que podamos encontrar esa información fácilmente. Esta etiqueta es el nombre de la variable y la cajita es el espacio utilizado en la memoria RAM.

Variables y constantes, cajas que guardan información
Variables y constantes, cajas que guardan información

Así son las variables en Javascript, y de hecho en cualquier lenguaje de programación. También puedes almacenar cualquier tipo de dato, ya sean números, cadenas de texto, funciones o cualquier tipo de objeto.

Existen dos maneras de declarar una variable. Una es usando let y la otra usando var.

//Variables de tipo String
var nombre = 'Jaime';
let apellido = "Cervantes"

La forma var es la que siempre ha existido desde el origen del lenguaje, después en el 2015 apareció let con la finalidad de mitigar ciertas deficiencias de var. Por esta razón, la recomendación es usar let, de todos modos es importante conocer bien var debido a que te encontrarás con mucho código que use aún var.

¿Cómo se usan las variables en Javascript?

El nombre o identificador de una variable, no puede iniciar con un número, debe iniciar con alguna letra (incluyendo _ y $), y además Javascript es sensible a mayúsculas y minúsculas. No es lo mismo nombre y Nombre, estas son dos variables diferentes.

Las variables se definen de dos formas:

Con un solo let:

let nombre = 'Jaime',
    apellidoPaterno = 'Cervantes',
    apellidoMaterno = 'Velasco';

Un solo var:

var nombre = 'Jaime',
    apellidoPaterno = 'Cervantes',
    apellidoMaterno = 'Velasco';

Ahora con un let por cada variable:

let nombre = 'Jaime';
let apellidoPaterno = 'Cervantes';
let apellidoMaterno = 'Velasco';

Con un var por cada variable.

var nombre = 'Jaime';
var apellidoPaterno = 'Cervantes';
var apellidoMaterno = 'Velasco';

En el ejemplo anterior se define variables de tipo string (cadenas de texto), pero recuerda que puedes almacenar cualquier tipo de dato.

Es recomendable poner cada variable en su propia linea porque es más rápido de entender para el “tú” del futuro y tus compañeros que no han tocado tu código. Por ejemplo NO definas las variables así:

let nombre = 'Jaime', apellidoPaterno = 'Cervantes', apellidoMaterno = 'Velasco'; edad = 32, sexo = 'H'

Probemos con números.

let edad = 32.9;
let hijos = 1;
var edad = 32.9
var hijos = 1;

Los datos boleanos solo almacenan falso o verdadero.

let esHombre = true;
let esAlto = false;
var esHombre = true;
var esAlto = false;

Como su nombre lo dice, su valor puede ser variable, por lo tanto puedes reasignar un nuevo valor a tu variable:

var nombre = 'Jaime';
nombre = 'Pepito';
nombre = 'Menganito';
console.log(nombre); // 'Menganito'

Ámbitos de las variables en Javascript, let vs var

La principal diferencia entre las variables definidas con let y var es el ámbito que alcanzan cuando se declaran:

  • Con let tienes ámbito de bloque {}.
  • Con var tienes ámbito de función function () pero no de bloque.

Ejemplo del ámbito de bloque.

if (true) {
  let nombre = 'jaime';
}
console.log(nombre); // Uncaught ReferenceError: nombre is not defined

Porque let tiene ámbito de bloque, al querer acceder a la variable nombre Javascript muestra:

Uncaught ReferenceError: nombre is not defined

Tratar de usar ámbito de bloque con var.

if (true) {
  var nombre = 'Jaime';
}
console.log(nombre); // 'Jaime';

En este ejemplo la variable nombre si puede ser accedida desde afuera de los bloques, esto es porque no está dentro de ninguna función.

Ahora vamos a declarar la variable con var dentro de una función.

function setearNombre() {
  var nombre = 'Jaime';
}
setearNombre();
console.log(nombre); // Uncaught ReferenceError: nombre is not defined

Vamos a ver que pasa con let cuando intentamos usar ámbito de función.

function setearNombre() {
  let nombre = 'Jaime';
}
setearNombre();
console.log(nombre); // Uncaught ReferenceError: nombre is not defined

Si definimos una variable con let dentro de una función, tampoco podemos acceder a ella desde afuera, porque la propia definición de la función usa bloques {}.

Constantes en Javascript

Las constantes son de igual forma que las variables, cajitas etiquetadas para guardar información, con la única diferencia que no se le puede reasignar algún otro valor.

const nombre = "Jaime";
nombre = "Pepito"; // Uncaught TypeError: Assignment to constant variable 

No nos olvidemos de declarar cada constante en su propia linea para mejorar la lectura de nuestro código.

const nombre = "Jaime";
const apellidoPaterno = "Cervantes";
const apellidoMaterno = 'Velasco';
const nombre = 'Jaime',
    apellidoPaterno = 'Cervantes',
    apellidoMaterno = 'Velasco';

Mismas reglas que las variables con let

Las constantes siguen las mismas reglas de ámbito de bloque que las variables en Javascript con let.

Así como let, const tiene ámbito de bloque, por lo que si se define una variable con var y tiene el mismo nombre de una constante, se lanzara el siguiente error:

{
  const nombre = "Jaime";
  var nombre = "Pepito"; // Uncaught SyntaxError: Identifier 'nombre' has already been declared
}

Constantes en javascript de solo lectura y mutables

Las constantes son de solo lectura, es por eso que no se les puede reasignar un valor. Aun así estos valores guardados siguen conservando el comportamiento de su tipo de dato.

Por ejemplo, si se crea una constante de un objeto, si se pueden cambiar las propiedades de ese objeto.

Un objeto literal en JavaScript no es más que un conjunto de valores identificados con un nombre o clave, como lo siguiente:

const jaime = {
  nombre: "Jaime",
  apellidoPaterno: "Cervantes",
  apellidoMaterno: "Velasco"
};
jaime.nombre = "Pepito"; // todo bien, se puede cambiar una propiedad
jaime = {}; // Uncaught Type Error: Assigment to constant variable

La referencia al objeto es de solo lectura, pero no quiere decir que el valor sea inmutable. Como se ve en el ejemplo, se puede cambiar la propiedad, pero no reasignar un nuevo valor a la constante jaime como podemos comprobar en la última línea del ejemplo.

¿Cómo nombrar a las variables y constantes en Javascript?

Cualquier tonto puede escribir código que una computadora pueda entender. Los buenos programadores escriben código que los humanos pueden entender.

Martin Fowler

El objetivo principal del nombre de variables y constantes es entender inmediatamente que es lo que estamos guardando en ellas. Es importante describir correctamente el contenido de las variables, porque así le facilitamos la vida a la siguiente persona que necesite leer y modificar nuestro código.

Esta siguiente persona muy a menudo eres “tú” del futuro, y al menos que seas un robot, no podrás recordar cada detalle de tu código.

La mayor parte del tiempo utilizado por un programador es para leer y entender código, no queremos gastar mucho tiempo descifrando el contenido de ellas, como programadores somos más contentos creando cosas nuevas.

Por esta razón no debe sorprendernos la importancia en el nombramiento de nuestras variables y constantes, es una de las mejoras maneras para no malgastar nuestro tiempo y el tiempo de nuestros colegas.

Recomendaciones:

  • Dedica el tiempo suficiente para nombrar una variable. Es como organizar tu cuarto, entre más ordenado más rápido será encontrar el otro calcetín (me pasa mucha cuando no ordeno el mío) xD.
  • El nombre debe ser muy semántico y explicar el contexto del mismo. Evitar nombres como data o info porque no dicen mucho del contexto, es obvio que son datos e información, pero ¿Para qué? o ¿Qué?.
  • Ponte de acuerdo con tus colegas sobre como nombrar las variables, en especial aquellas que tiene mucha relación con el negocio. Por ejemplo para los datos de registro de un usuario, puede ser “registroDeUsuario”, “registroDeCliente”, “registroDeVisitante”, las tres formas pueden ser válidas (depende mucho el contexto), pero es mejor seguir una convención a través de un acuerdo.
  • Evita los nombres muy cortos como “u1”, “a2”, “_”, “_c”
  • Evita las abreviaciones, prefijos, sufijos e infijos. Estos son difíciles de entender. Por ejemplo pmContenido, ¿Qué es “pm”?
  • Si la variable o constante tiene un ámbito muy pequeño, es decir, es utilizada en líneas cercanas, entonces el nombre puede ser corto.
  • Si la variable o constante se utiliza en un ámbito grande, es decir, se hace referencia a ella a una distancia de muchas líneas, entonces el nombre debe ser muy descriptivo, y por lo tanto es normal que pueda ser largo.

Publicaciones relacionadas

https://www.pensemosweb.com/introduccion-javascript/
https://www.pensemosweb.com/javascript-scheme-self-java/
¿Cómo crear un componente web?  Shadow DOM y etiqueta template

¿Cómo crear un componente web? Shadow DOM y etiqueta template

¿Cómo logramos encapsulación de nuestros nuevos elementos HTML que no provoque conflictos con el resto de una aplicación?, es decir, que los estilos, HTML interno y JS de un componente web, no colisionen con el resto.

La respuesta básicamente es, el Shadow DOM. El Shadow DOM nos permite tener los estilos y contenido HTML interno totalmente aislado del exterior. También se ayuda del API del DOM y custom elements para establecer comunicación con otros elementos y componentes internos. Además con el exterior.

El amor es de naturaleza interna. No lo vas a encontrar en alguien más, naces con ello, es un componente interno de cada persona. Mira a un bebé, simplemente es feliz por el hecho de existir. Irradia amor, te lo comparte.

Anónimo

Antes de comenzar, si aún no estás familiarizado con los custom elements y los componentes web nativos en general, te recomiendo revisar primero estas publicaciones:

  1. Componentes web, ¿Qué son? POO
  2. ¿Cómo crear un componente web? Elementos HTML personalizados

Primero vamos a revisar la etiqueta <template>, para luego utilizarla dentro de nuestro componente web usando shadow DOM.

¿Qué es una plantilla?

Un template o plantilla HTML con el tag <template>, es una forma de crear elementos visuales de manera sencilla y eficiente, de tal forma que se pueden definir partes de una página web o aplicación a las cuales les podemos insertar valores.

Este template puede ser reutilizado en diferentes vistas de una aplicación. Tal vez estés familiarizado con jade, pug, mustache, o con algunos frameworks y librerías como angular y vue, los cuales utilizan templates. También si has utilizado wordpress y php, los archivos de los temas serian una especie de templates.

Ahora, los templates que vamos a aprender son específicos de HTML y por lo tanto funcionan en los navegadores web. Para entrar un poco más en contexto veamos un ejemplo sencillo de como podemos crear varios elementos para mostrar un saludo, utilizaremos estas tres fuciones, que forman parte del API del DOM.

  • document.createDocumentFragment()
  • document.createElement('nombreTag', [opciones])
  • nodo.appendChild(nodo)

Queremos mostrar en nuestra aplicación un saludo al usuario y lo hacemos de la siguiente forma:

Ahora veamos como hacer lo mismo, pero utilizando la etiqueta <template>:

Como podemos ver, con la etiqueta <template> se usa mucho menos código, si el contenido de lo que queremos mostrar crece, solo debemos agregar las etiquetas y demás contenido de manera sencilla dentro de la etiqueta <template>. Pero si usamos el primer método, nuestro código javascript crecerá mucho más, esto es más difícil de entender y mantener con el tiempo.

La etiqueta template usa internamente un DocumentFragment, la ventaja de utilizar document fragments es que su contenido no es aún agregado al árbol de nodos, sino que se encuentra en memoria y se inserta una sola vez al final, cuando el contenido esta completo.

// Agregar document fragment a la página
host.appendChild(df);

Existen 4 puntos importantes al utilizar la etiqueta <template> para crear plantillas:

  • La etiqueta <template> no se pinta en nuestra aplicación, por default tiene un display: none;.
  • El contenido de la etiqueta <template> es un document fragment, el cual tiene la característica de no estar insertado en el árbol de nodos. Esto es bueno para el rendimiento de la aplicación debido al alto costo de pintar elementos.
  • El código JS necesario es muy simple y corto
  • Cuando clonamos el contenido de nuestro template usando cloneNode(), debemos pasar el valor true como parámetro, de otra manera NO clonaríamos los elementos hijos del document fragment de nuestro template.

Shadow DOM

El shadow DOM es un DOM o árbol de nodos en las sombras, escondidos de los demás elementos de una aplicación. Para entender esto vamos a crear nuestro primer componente web utilizando el estándar de custom elements, la etiqueta <template> y el shadow DOM.

Nuestro componente se llama mi-saludo, con el contenido del ejemplo anterior:

Si aún no sabes que son los custom elements, sigue este link. En el ejemplo anterior creamos un custom element llamado mi-saludo, dentro del constructor() creamos una instancia del template.

// Obtengo la única etiqueta 'template'
const tpl = document.querySelector('template');
// Clono su contenido y se crea una instancia del document fragment
const tplInst = tpl.content.cloneNode(true);

Luego creamos un shadow DOM y lo adjuntamos al custom element mi-saludo:

// Se crea un nuevi shadow DOM para las instancias de mi-saludo
this.attachShadow({ mode: 'open'});

Y finalmente agregamos el contenido clonado del template dentro del shadow DOM:

// Y se agrega el template dentro del shadow DOM usando el elemento raíz 'shadowRoot'
this.shadowRoot.appendChild(tplInst);

En este último paso usamos la propiedad shadowRoot, creada cundo invocamos attachShadow, esta propiedad hace referencia a un nodo raíz especial de donde se empieza a colgar todo lo que definimos dentro de nuestra etiqueta <template>, y es a partir de este nodo que agregamos contenido.

Lo que se agrega al shadow DOM esta oculto para el exterior, ningún document.querySelector() externo puede acceder a los elementos del shadow DOM y los estilos definidos en el exterior no pueden acceder a estos elementos ocultos. Esto quiere decir que dentro del shadow DOM podemos utilizar atributos id y name sin temor a colisionar con los ids y name del exterior.

Cuando creamos un shadow DOM, se hace con la propiedad mode: open, de esta manera se puede acceder al contenido del shadow DOM usando Javascript y el atributo shadowRoot. Sin el atributo shadowRoot es imposible acceder a los elementos y eso nos proporciona encapsulación a nuestro componente.

Para crear una nueva instancia de nuestro nuevo componente, solo utilizamos el nombre que registramos como etiqueta:

// Se registra el custom element para poder ser utilizado declarativamente en el HTML o imperativamente mediante JS
customElements.define('mi-saludo', MiSaludo);

Y en el html utilizamos código declarativo:

<mi-saludo></mi-saludo>

¿Qué pasa si a nuestro template le agregamos una etiqueta style? Debido a que las instancias de nuestro template son agregadas al shadow DOM, entonces esos estilos solo afectan dentro del shadow DOM.

Para establecer estilos dentro del shadow DOM al nuevo tag creado, se utiliza la pseudoclase :host, que hace referencia a la etiqueta huésped del shadow DOM, es decir, mi-saludo.

También agregamos en el exterior estilos para las etiquetas <h1>, estos estilos no pudieron afectar al contenido interno del shadow DOM, el único h1#mi-id con borde rojo es el que se encuentra fuera de nuestro componente. El h1 externo como el interno tienen el mismo id, a causa del encapsulamiento con shadow DOM, no colisionan los estilos con el mismo selector de id.

Ahora en nuestro código html, agreguemos más etiquetas mi-saludo:

Como podemos ver la combinación de estas tres tecnologías; custom elements, template y shadow DOM nos permite crear nuevas etiquetas personalizadas con su propio contenido interno sin afectar el exterior y también el exterior no puede afectar directamente la implementación interna de nuestro componente. En el último ejemplo podemos ver como creamos cuatro instancias de nuestro componente simplemente utilizando etiquetas, es decir, reutilizando nuestro componente encapsulado.

En la próxima publicación veremos como implementar más funcionalidad Javascript encapsulada del componente web y también más características muy útiles del shadow DOM como composición, eventos y más estilos. Finalmente veremos los módulos de JS para completar un componente web reutilizable que pueda ser importado en cualquier otra aplicación web.

Javascript Event Loop, concurrencia y asíncrono

Javascript Event Loop, concurrencia y asíncrono

Javascript es secuencial

“Event loop”, tal vez lo has escuchado. Esto es porque javascript tiene modelo de ejecución de código donde utiliza un ciclo de eventos (event loop), que una vez entendiendo su funcionamiento, se puede crear código mucho más eficiente, y resolver problemas rápidamente, que a simple vista no son tan obvios. Es la base de la concurrencia y el asincronismo.

Para comenzar:

  • Javascript se ejecuta en un solo hilo de ejecución, como se ejecuta un solo hilo, su ejecución es secuencial, no es como JAVA donde se pueden lanzar otros hilos.
  • Existen ambientes huéspedes donde se ejecuta javascript que tienen su propia API, ejemplo:
    • Navegador web
    • Node.js
  • Este funcionamiento secuencial es el mismo, no cambia por estar relacionado con un ambiente huésped.
  • La concurrencia se logra a través de invocaciones asíncronas a la API del ambiente huésped, esto evita que el funcionamiento se bloquee y provoque lentitud de las aplicaciones.

Elementos para la concurrencia

Veamos con más detalle los elementos que hacen funcionar Javascript en los ambientes huéspedes.

  1. Call Stack – Pila de ejecuciones
  2. Callback Queue – Cola de retrollamadas, tambien llamado Task Queue
  3. Event Loop – Ciclo de eventos
  4. Host Environment – Funcionalidad del ambiente huésped
Event loop, ciclo de eventos javascript
Event loop, ciclo de eventos javascript

Calls Stack, heap y motor de javascript

Del lado izquierdo tenemos al motor de javascript, este motor se encarga de ejecutar nuestro código. A veces es llamado máquina virtual, porque se encarga de interpretar nuestro código a lenguaje máquina y ejecutarlo. Solo puede ejecutar una línea de código a la vez. Tiene un Heap para guardar objetos y demás datos en memoria que el código pueda generar.

Pero también tenemos una Pila de llamadas (Call Stack). Los elementos de la pila son objetos y datos de memoria, pero tienen una estructura especifica. Tienen un orden y el último elemento en entrar es el primero en salir, se le llama LIFO (Last-in, First-out).

Cada elemento de la pila es un trozo de código Javascript y guarda las referencias del contexto y variables utilizadas. La pila ejecuta el trozo de código en turno hasta quedar vacía.

Callbacks Queue

Abajo tenemos Callbacks Queue, es una estructura de datos y guarda pedazos de código que el motor de javascript puede ejecutar. Es una cola, y como la cola de las tortillas el primero en formarse, es el primero en salir (FIFO, First-in, First-out).

Un callback, es una función que se pasa por parámetro a otra, de tal manera que esta última ejecuta el callback en un punto determinado, cuando las condiciones y/o los datos cumplen una cierta regla de invocación.

Event loop

Event Loop, este elemento nos permite mover la siguiente callback del Callbacks Queue al Call Stack. Como es un ciclo, en cada iteración toma un solo callback.

Ambiente huésped

Del lado derecho tenemos El ambiente huésped, el ambiente huésped es el lugar donde corremos Javascript, los más comunes son el Navegador web y Node.js. Las tareas que ejecuta el ambiente huésped son independientes de los demás componentes. En el navegador web tiene sus propias API como fetch, setTimeout, alert. Node.js también cuenta con su propia API como net, path, crypto.

¿Cómo es posible?, ¿Y la concurrencia? ¿Puedo hacer muchas cosas a la vez en un sitio web?

Todo el trabajo de poner un temporizador, renderizar contenido como texto, imágenes, animaciones, o ver videos, están a cargo del ambiente huesped. Lo que pasa es que las APIs del navegador web generan callbacks cuando su tarea ya está completada y los agrega al Callbacks Queue para que el event loop las tome y las mueva al Call stack.

Javascript se comunica con el ambiente huésped a través de su API, mas adelante vamos a utilizar un ejemplo basado en el diagrama de arriba, donde se utiliza window.setTimeout, la cual es una función del navegador web.

Concurrencia

Inicio es la función principal, con la que se inicia el proceso de ejecución en el call stack. Dentro de inicio tenemos cuatro setTimeout de dos segundos. La función setTimeout es propia del ambiente huésped, y a los cuatro setTimeouts le pasamos un callback, estos son, cb1, cb2, cb3 y cb4.  

Los temporizadores setTimeout se ejecutan en la parte de la funcionalidad del ambiente huésped dejando tiempo de ejecución para el call stack. He aquí la concurrencia, mientras el ambiente huésped ejecuta un temporizador, el call stack puede continuar ejecutando código.

Después de los primeros dos segundos, el ambiente huésped, a través del primer setTimeout pasa cb1 al callback queue, pero durante estos dos segundos se pueden realizar otras operaciones en el ambiente huésped, en el caso del navegador, un ejemplo seria que el usuario puede seguir escribiendo dentro de un textarea. De nuevo, he aquí la concurrencia.

Tras el primer setTimeout, el segundo setTimeout pasa cb2 al callback queue y así sucesivamente hasta el cb4. Mientras los callbacks se pasan al callback queue. El event loop nunca deja de funcionar, por lo que en alguna iteración el event Loop pasa cb1 al call stack y se empieza a ejecutar el código de cb1 y esto mismo sucede con cb2, cb3 y cb4.

Event loop en acción

Aquí abajo esta el ejemplo del que hablamos, el temporizador genera los callbacks en el orden que aparecen los setTimeouts más dos segundos. El Event Loop toma un callback en cada iteración, por lo que cada cb de setTimeout se invocan uno después del otro, y no exactamente después de dos segundos, pues entre que se posicionan los callbacks en la cola y se mueven al call stack de ejecución transcurre más tiempo. Esto sin contar el tiempo que trascurre entre cada invocación de setTimeout.

Si en los cuatro callbacks se indica el mismo tiempo en milisegundos. Presiona el botón RERUN, Te darás cuenta de que los tiempos no son iguales y que pueden pasar más de dos segundos.

Conclusión

  1. Las APIs huéspedes reciben callbacks que deben insertar en la callback queue cuando el trabajo que se les encomienda esta hecho, por ejemplo un callback de una petición ajax se pasa a la callback queue solo cuando la petición ya obtuvo la respuesta. En el ejemplo que describimos arriba con setTimeout, cuando pasan dos segundos, se inserta el callback al callback queue.
  2. Mientras el ambiente huésped realiza sus tareas, en este caso, los demás temporizadores, el call stack de ejecución de javascript puede ejecutar el código que el event loop le puede pasar desde el callback queue.
  3. El Event loop es un ciclo infinito que mientras existan callbacks en el callback queue, pasara cada callback, uno por uno, al call stack.
  4. El call stack no puede ejecutar más de un código a la vez, va ejecutando desde el último elemento hasta el primero, en nuestro caso hasta la terminación de la función inicio que dio origen a todo el proceso. Es una estructura de datos tipo Pila.

Gracias a que los callbacks se ejecutan hasta que un trabajo específico esté terminado, proporciona una manera asíncrona de ejecutar tareas. Las tareas pueden realizarse en el ambiente huésped sin afectar la ejecución del call stack. En la pila solo se ejecuta código que recibe el resultado de las tareas realizadas por el ambiente huésped.

es_MXES_MX