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 tiposdatos primitivos en Javascript.
Number, números como; 1, 0, 18500, 89.95124
BigInt, agregado en el 2020, para representar números enteros muy, pero muy grandes, 99999999999999n
String, cadena de caracteres como 'Hola' y "Buenas noches".
Boolean, solo acepta true o false, es decir, si o no.
null, sirve para indicar que algo es nada, su único valor es null.
undefined, sirva para indicar que algo aún no está definido.
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 NaN25*{};// es igual a NaN
Todo operación que tenga NaN como uno de sus operandos, dará como resultado NaN.
25*NaN;// es igua a NaN25+NaN;// es igual a NaN
NaN ni siquiera es igual a NaN
NaN===NaN;// es igual a falseNaN==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 globalisNaN('jaime');// true --> Esto esta mal, deberia seguir regresando false// lo que realmente hace es una conversion automaticaconst result =Number('jaime');// NaNisNaN(result);// trueNumber.isNaN('343434');// falseNumber.isNaN('jaime');//falseNumber.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.
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.
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.9498743710662Math.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;// undefinedlet edad;// undefinednombre ===undefined;// trueedad ===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:
Con comillas simples, ''. Recomendada si no necesitas evaluar expresiones dentro.
Comillas dobles, ""
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:
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 =newString('Jaime Cervantes Velasco');nuevoString.length;// 23(5).toFixed(2);// 5.00// Igual a groso modo pasa algo asi:const numero =newNumber(5);numero.toFixed(2);// 5.00false.toString();// 'false'// Igual a groso modo pasa algo asi:const booleano =newBoolean(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.
En el ejemplo tenemos un objeto persona, tiene varias propiedades de diferentes tipos, su nombre es un String, su edad es un Number, getNombre 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 33persona['edad'];// regresa 33// obtenemos la funciónn con corcheteds y la invocamos con parentesisconst frase = persona['hablar']();console.log(frase);// 'Hola soy Jaime Cervantes Velasco, tengo 29 años.'
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 =newDate();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);// falseBoolean(null);// falseBoolean(undefined);// falseBoolean(0);// falseBoolean(0n);// falseBoolean(NaN);// falseBoolean('');// false// Todos los demas valores van a regresar trueBoolean('');// trueBoolean('false');// trueBoolean('jaime cervantes');// trueBoolean({});// trueBoolean([]);// 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.
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.
typeof5;// 'numbertypeofNaN;// 'number'typeof99999999999999999999999999999999999999999999999n;// 'bigint'typeoftrue;// 'boolean'typeofnull;// 'object', esto es un error que existe desde la primera versión de JStypeofundefined;// 'undefined'typeofSymbol('simbolo');// 'symbol'typeof'cadena de caracteres';// 'string'typeof{};// 'object'typeof [];// 'objecttypeofnewDate();// 'object'typeofnewString('Jaime');// Objecttypeoffunction(){}// 'function'typeofclassA{}// '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.
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.
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.
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
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
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, 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.
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 breakdentro de un ciclo for anidado, ese breakrompe 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.
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.
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 JavaScriptV8, el cual lo utiliza node.js, mongoDB yGoogle 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 personaconst 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.
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.
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
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
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 Stringvar 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.9var 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) {letnombre='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) {varnombre='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.
functionsetearNombre(){varnombre='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.
functionsetearNombre(){letnombre='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:
{constnombre="Jaime";varnombre="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 propiedadjaime ={};// 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.
“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.
Call Stack – Pila de ejecuciones
Callback Queue – Cola de retrollamadas, tambien llamado Task Queue
Event Loop – Ciclo de eventos
Host Environment – Funcionalidad del ambiente huésped
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 Callstack.
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 cb1al 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 cb2al 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 cb1al call stack y se empieza a ejecutar el código de cb1y 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 callstack 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
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.
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.
El Event loop es un ciclo infinito que mientras existan callbacks en el callback queue, pasara cada callback, uno por uno, al call stack.
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.
Redux es un contenedor predecible del estado de aplicaciones JavaScript.
Te ayuda a escribir aplicaciones que se comportan de manera consistente, corren en distintos ambientes (cliente, servidor y nativo), y son fáciles de probar. Además de eso, provee una gran experiencia de desarrollo, gracias a edición en vivo combinado con un depurador sobre una línea de tiempo.
redux.org
Te permite controlar el flujo de datos de una aplicación Javascript, este flujo de los datos funciona en una sola dirección. Por esta única dirección es mucho más fácil controlar las mutaciones y operaciones asíncronas.
Redux es parecido a flux, de hecho está basado en flux
En el estado de una aplicación, principalmente en aplicaciones SPA, esto es muy importante, porque las aplicaciones web modernas tienen gran complejidad de operaciones asíncronas. Además de controlar el estado entre los componentes.
¿Qué problemas puedo mitigar con Redux?
En el ejemplo de aplicaciones web, estas necesitan controlar peticiones a servidores, obtener datos y controlar el estado en el cliente. Aun cuando estos datos no han sido guardados en el servidor.
Estas aplicaciones modernas requieren de un control complejo y por eso nuevos patrones de arquitectura como Redux y flux nacen para hacer el desarrollo más productivo.
Las mutaciones de objetos son difíciles de manejar y más a escalas medianas y grandes. Se comienza a perder el control de los datos, este flujo de datos generan comportamiento no deseados. Mala información desplegada al usuario y código que es muy difícil de mantener.
Sin contar cosas más elevadas como tu salud y causa de estrés por el esfuerzo en arreglar estas inconsistencias, te hace perder tiempo para mantener la aplicación funcionando correctamente. Por si fuera poco afectas a tus usuarios porque ellos obtienen información incorrecta y pierden su tiempo al utilizar esa información.
¿Qué decir de las operaciones asíncronas? Bueno, prácticamente pasa lo mismo o aún peor. Porque en una operación asíncrona no sabes cuando obtendrás el resultado y además normalmente vienen acompañadas con el deseo de hacer modificaciones al estado de la aplicación.
Un ejemplo común es el control de los datos de tu aplicación con cualquier frawework front-end de componentes visuales.
¿De qué está hecho Redux? Tres elementos
Store. El store es un objeto donde guardas toda la información del estado, es como el modelo de una aplicación, con la excepción que no lo puedes modificar directamente, es necesario disparar una acción para modificar el estado.
Actions. Son el medio por el cual indicas que quieres realizar una modificación en el estado, es un mensaje o notificación liviana. Solo enviando la información necesaria para realizar el cambio.
Reducers. Son las funciones que realizan el cambio en el estado o store, lo que hacen internamente es crear un nuevo estado con la información actualizada, de tal manera que los cambios se reflejan inmediatamente en la aplicación. Los reducers son funciones puras, es decir, sin efectos colaterales, no mutan el estado, sino que crean uno con información nueva.
¿Qué principios debo seguir? Tres principios
Un único Store para representar el estado de toda la aplicación. Tener una sola fuente de datos para toda tu aplicación permite tener centralizada la información, evita problemas de comunicación entre componentes para desplegar los datos, fácil de depurar y menos tiempo agregando funcionalidad o detectando errores.
Estado de solo lectura. Esto permite tener el control de cambios y evitar un relajo entre los diferentes componentes de tu aplicación, ni los componentes, ni peticiones ajax pueden modificar directamente el Estado (state) de tu aplicación, esto quiere decir que si quieres actualizar tu estado, debes hacerlo a través de actions, de esta manera redux se encarga de realizar las actualizaciones de manera estricta y en el orden que le corresponden.
Los cambios solo se hacen con funciones puras. Al realizar los cambios con funciones puras, lo que realmente se hace es crear un nuevo objeto con la información actualizada, estas funciones puras son los reducers y no mutan el estado, al no mutar el estado, se evitan problemas de control, datos incorrectos, mal comportamiento y errores, también permite que la depuración sea más fácil. Puedes dividir los reducer en varios archivos diferentes y las pruebas unitarias son fáciles de implementar. Los reducers son funciones puras que toman el estado anterior y una acción, y devuelven un nuevo estado.
Otras arquitecturas como MVC (Modelo, Vista, Controlador), los cambios pueden existir en ambas direcciones, es decir, la vista puede cambiar el estado, el modelo lo podría modificar y también el controlador. Todos estos cambios necesitan estar sincronizados en varias partes de una aplicación para evitar inconsistencias, lamentablemente este problema de inconsistencia se vuelve muy difícil y tedioso de resolver.
Lo anterior no sucede con Redux.
¿Cómo funciona? Mi primer estado predecible con Redux
Primero veamos el flujo de una sola dirección de redux:
Redux: flujo de control una sola dirección
Ahora un ejemplo concreto para saber de que estamos hablando, el control de un contador:
Si quieres probar este ejemplo en tu máquina recuerda insertar o importar la librería Redux. En este ejemplo podemos ver el funcionamiento de Redux, cada vez que se da clic sobre los botones de incrementar y decrementar, el contador se incrementa y decrementa.
Si ahora nos vamos al código JS, podremos ver que solo tenemos una función llamada counter, esta función es un reducer y es una función pura, sin efectos colaterales.
Luego vemos que como parámetros recibe un state y un action, cuando creamos nuestro Store con Redux, estas funciones reducer son utilizadas para modificar el State.
Normalmente, el state, reducer y action son definidos en puntos diferentes, y pueden ser organizados en varios archivos y carpetas dependiendo de la necesidad de la aplicación, toda esta organización tiene la finalidad de tener un código limpio y separar funcionalidades. En este caso sencillo no es necesario, en un solo archivo tenemos todo.
State
Nuestro state es solo un valor numérico, el cual se va a incrementar o decrementar con los botones que están en la página, normalmente el state es un objeto literal o un array literal, pero en nuestro caso solo necesitamos un número. Su definición podría estar en otro archivo, sin embargo, para este ejemplo no es necesario, lo agregamos como valor por defecto del parámetro state.
functioncounter(state=0,action){...
Actions
Si seguimos revisando nuestro código, veremos unos cuantas condiciones que revisa el valor del parámetro action y dependiendo de la acción, se ejecuta alguna operación, en nuestro caso son las operaciones INCREMENTAR o DECREMENTAR.
Los actions no son más que objetos literales como los siguientes:
Los reducer revisan esos actions, en nuestro ejemplo:
if (action.type ==='INCREMENTAR') {returnstate+1;}if (action.type ==='DECREMENTAR') {returnstate-1;}
Con todo esto ya tenemos nuestro State y su estado inicial en caso de no estar definido, también nuestra primer reducer, counter, y nuestros primeros actions INCREMENTAR y DECREMENTAR.
Store
Es el momento de crear nuestro Store, utilizando la librería redux esto es muy fácil:
const store = Redux.createStore(counter);
Con la anterior línea, Redux crea un Store para controlar el estado de nuestra aplicación. Internamente, un Store es un Patrón observador que utiliza un Singleton para el State y expone los siguientes métodos principales:
store.getState()
store.subscribe(listener)
store.dispatch(action)
store.getState() te permite obtener el estado actual de tu aplicación.
store.subscribe(listener) ejecuta la función listener (u observador), cada vez que el store es actualizado.
store.dispatch(action) pide actualizar el estado, esta modificación no es directa, siempre se realiza a través de un action y se ejecuta con un reducer.
Reaccionar a cambios del state
Luego creamos una función render, para que se ejecute cada vez que el State de tu aplicación cambie.
Aquí te preguntarás, ¿Cómo es posible que esa línea imprima 0 en la pantalla?, pues internamente el store invoca un dispatch con un action vacío store.dispatch({}), esto invoca nuestra función reducer y al no encontrar ninguna acción, entonces regresa el estado inicial 0.
Luego nos subscribimos a store para escuchar u observar el State cada vez que se actualice y poder ejecutar la función render().
store.subscribe(render);
Esta línea permite que cuando demos clic en los botones de incrementar y decrementar, se imprima el nuevo valor en la página volviendo a renderizar su contenido.
Ejecutar dispatch a través de eventos del DOM
Y por último agregamos dos listeners correspondientes a los botones de incrementar y decrementar, cada botón realiza una invocación dispatch para modificar el estado.
Al fin tenemos el flujo de datos de nuestro sencillo ejemplo usando Redux, en general los componentes que forman a una aplicación que utiliza Redux son los siguientes:
Redux: flujo de control completo
Conclusiones
Podemos notar el flujo en una sola dirección desde la vista con los actions dispatch(action), luego los reducers counter(prevState, action) para modificar el store y este último manda la información a través de subscribe(render) y getState().
Redux como ya se mencionó en párrafos anteriores, nos proporciona:
El conocimiento en todo momento del estado de nuestra aplicación y en cualquier parte de la aplicación con mucha facilidad.
Fácil organización, con actions, reducer y store, haciendo cambios en una sola dirección, además puedes separar tus actions, reducer y el store en varios archivos.
Fácil de mantener, debido a su organización y su único store, mantener su funcionamiento y agregar nuevas funcionalidades es muy sencillo.
Tiene muy buena documentación, su comunidad es grande y tiene su propia herramienta para debuguear
Todos los puntos anteriores hacen que sea fácil de hacer pruebas unitarias.
Y lo más importante, te ahorra tiempo, dinero y esfuerzo xD.
El origen de la programación funcional data del año 1936, cuando un matemático llamado Alonzo Church necesitaba resolver un problema complejo, del cual no hablaremos aquí, pero Alonzo creó una solución, el cálculo lambda. De esta forma nació la programación funcional, incluso mucho antes de la primera computadora digital programable y de los primeros programas escritos en ensamblador.
La programación funcional es mucho más antigua que la programación estructurada y la programación orientada a objetos. Te recomiendo también revisar esta información sobre el paradigma de programación funcional.
Sin efectos colaterales
Todo lo que necesitas saber sobre programación funcional es que “No tiene efectos colaterales“, es decir, que sus elementos (funciones) son inmutables.
Lo anterior significa que una función no se mete en lo absoluto con datos que existen fuera de ella, si puede utilizar los datos enviados como parámetros, pero no debe modificarlos, todos los datos deben ser inmutables, ¿Por qué?, pues porque así hacemos las cosas más simples y con menos errores, más adelante tenemos un ejemplo. A este comportamiento se le llama puro.
Sin efectos colaterales nos permite entender todas las demás características de la programación funcional, por mencionar algunas principales:
Funciones puras
Inmutabilidad
Funciones de alto nivel
Funciones currificadas
Recursividad
Menos errores de programación porque no realiza efectos colaterales.
Toda función teniendo ciertos parámetros, cuando se ejecuta, obtendremos un resultado basado en esos parámetros, y si volvemos a utilizar los mismos parametros, entonces la función regresara el mismo valor. Esto es debido a que una función no depende de más datos externos que puedan ser modificados, así tendremos más seguridad y nuestro código se vuelve más entendible.
Bueno, realmente ya explicamos de manera indirecta que es una función pura en los párrafos anteriores, pero veamos una definición formal. Una función pura es la que cumple con estas dos características:
Siempre obtendremos el mismo resultado dado los mismos parámetros de entrada.
No tiene ningún efecto colateral observable, no modifica el estado de los datos externos, por lo que todos los datos deben ser inmutables.
Un ejemplo de como cambiar el nombre de una objeto persona usando primero una función con efector colaterales:
En la anterior función notamos que se cambia objeto jaime, el cual supongamos que es parte del estado de nuestra aplicación, pero puede haber otras funciones que utilicen este objeto, por lo que se puede generar problemas si alguien más espera que la propiedad nombre del objeto siga siendo 'jaime'.
En la versión funcional, cambiarNombre no modifica el objeto jaime, más bien crea un nuevo objeto con la misma edad que jaime y con la propiedad nombre igual a 'Juan', con esto evitamos efectos colaterales por si el objeto jaime es utilizado por otra función u otro programador.
Con esta función sin efecto colateral, nos damos cuenta de que los datos se manejan sin ser modificados, es decir, inmutables, los parámetros persona y nombre nunca fueron cambiados.
Funciones de alto nivel y currificadas
Una función de alto nivel es una función que implementa al menos una de las opciones siguientes:
Recibir como parámetro una o más funciones
Regresar como resultado una función
Un ejemplo muy común usado en nuestro navegador web es agregar escuchadores de eventos:
<buttonid="boton">Soy un botón</button>const boton = document.querySelector('#boton');boton.addEventListener('click',function(){alert('Click sobre el boton');});
En el código pasamos como segundo parámetro del método addEventListener unafunción anónima (sin nombre) que mostrará un alerta al dar click sobre un botón con id igual a ‘botón’. Dado que pasamos por parámetro una función, entonces se dice que es una función de alto nivel.
Otro ejemplo de función de alto nivel lo podemos observar en los métodos de arreglos en Javascript, el código de abajo toma un arreglo de números y crea otro arreglo con los valores aumentados al doble.
La función map retorna un nuevo arreglo, no cambia el arreglo original, por lo que decimos que no existe un efecto colateral.
Las funciones currificadas son funciones de alto nivel que como resultado regresan otra función, de tal manera que el acto de currificar es convertir una función de más de un parámetro en dos o más funciones que reciben parcialmente esos parámetros en dos o más invocaciones currificadas.
Las funciones flecha o arrow functions nos permiten acceder al contexto de los parámetros, es por eso que seguimos teniendo acceso al parámetro a de la primera invocación de summaryCurry. De esta manera cuando se les define un valor, una arrow function puede ver ese valor. Para lograr lo mismo sin funciones flecha se debe utilizar la variable argumentsque existe como variable local dentro de todas las funciones en JavaScript. Para mantener las cosas simples, de momento no veremos como hacerlo con arguments.
Función recursiva
Para crear una función recursiva, primero se define un caso base, luego a través de la división del problema en pedazos más pequeños se encuentra un patrón, este patrón llamado caso recursivo se repite muchas veces, es aquí donde la función se llama así misma y acumula los resultados, la ejecución se detiene hasta llegar a su caso base.
El caso base, el cual permite detener la ejecución de subsecuentes invocaciones de la función recursiva.
El caso recursivo, el cual permite que una función se llame a sí misma hasta llegar al caso base.
El factorial de un número positivo es la multiplicación de ese número por el número inmediato menor y así sucesivamente hasta llegar al número 1, su notación es n!, donde n es un número positivo. Por ejemplo el factorial de 5 es 120, 5! = 5 x 4 x 3 x 2 x 1 = 120.
// caso base:1!=1=1// caso recursivo, ejemplos:2!=2 x 1=2 x 1!3!=3 x 2 x 1=3 x 2!4!=4 x 3 x 2 x 1=4 x 3!5!=5 x 4 x 3 x 2 x 1=5 x 4!
Con estos datos, podemos crear nuestra formula factorial(n) = n * factorial(n-1), lo cual seria nuestro caso recursivo, pero debemos de añadir nuestro caso base para que se detenga, cuando n=1 debemos obtener como resultado 1.
Veamos como quedaría nuestra función recursiva en javascript:
El patrón de diseño observador, define una dependencia de uno a muchos objetos de tal manera que cuando un objeto cambio de estado, todos sus dependientes son notificados y actualizados automáticamente
Del libro “Design patterns: elements of reusable object-oriented software”
Si has utilizado eventos del navegador, como escuchar el evento click sobre un botón, ahí estás utilizando el patrón observador, tu función callback es tu objeto observador y el botón es el sujeto, tu función callback esta interesada en la actividad de dar click sobre el botón.
El Sujeto está compuesto por las siguientes métodos importantes que usaremos en nuestro código javascript:
Una colección de observers
Método attach()
Método detach()
Método notify()
Normalmente un Observador contiene un método update() que el Sujeto utiliza.
Para tener más claro esto, vamos a ver dos ejemplos del patrón observador, el primero será el Observador puro y el segundo en su modo Publicador/Suscriptor.
Simplificando el observador
Simplificando el diagrama anterior, el patrón observador funciona así:
Patrón de diseño observador simplificado
Juega con el ejemplo de abajo, y agrega nuevos checkbox, si has agregado más de un nuevo checkbox, podemos notar que cuando damos click en el checkbox ‘Seleccionar‘ todos los demás checkbox nuevos se enteran de esa actividad y se actualizan automáticamente.
Todo esto sin necesidad de estar al pendiente de que el checkbox ‘seleccionar‘ ha cambiado su estado, el encargado de notificar del cambio es el Sujeto y solo si existe algún cambio. Esto es muy eficiente si tienes cientos o miles de checkbox observando ese cambio.
Si das click sobre uno de los checkbox, se ejecuta el método subject.detach(), entonces ese checkbox ya no es un observador, por lo que si ahora activas y desactivas el checkbox seleccionar nunca es notificado sobre el cambio.
Publicador/suscriptor simple
El patrón de diseño publicador/suscriptor es una variación del observador, en este modo el suscriptor u observador se suscribe a una actividad o evento del publicador o sujeto.
El publicador notifica a todos los objetos suscritos cuando el evento al que están interesados se dispara o publica.
El publicador está compuesto por las siguientes métodos importantes que usaremos en nuestro código javascript:
Una colección de observers o subscribers
Método subscribe() o attach()
Método unsubscribe() o detach()
Método publish() o notify()
Aquí dejo el diagrama simple de como funciona esta versión:
Patrón de diseño publicador/suscriptor simplificado
El publicador/suscriptor se parece más a los eventos del DOM y a eventos personalizados, pero no deja de ser el patrón de diseño Observador. Ademas, como observadores, podemos suscribirnos a más de una actividad o evento.
En el ejemplo, el método suscribe agrega la función observadora, esta última se ejecuta cuando la actividad click es publicada.
En conclusión, el patrón de diseño observador es muy útil, se puede modificar o adecuarlo a ciertas necesidades, un ejemplo es la variacion publicador/suscriptor. Se usa en:
ReactveX.
Librerias y frameworks de componentes web tales como angular, react, lit, vue, svelte utilizan este patrón para su bindeo de datos.
Navegadores web cuando usamos eventos
Node.js cuando utilizamos EventEmitter al recibir una petición a nuestro servidor o al leer un archivo del disco duro.
Flux y redux para manejar cambios en el estado de nuestra aplicación.
Para crear un servidor web en node.js, primero, ¿Qué es node.js?, tomando la definición del sitio oficial, Node.js®es un entorno de ejecución para JavaScript construido con el motor de JavaScript V8 de Chrome. Node.js usa un modelo de operaciones E/S sin bloqueo y orientado a eventos, que lo hace liviano y eficiente. El ecosistema de paquetes de Node.js, npm, es el ecosistema más grande de librerías de código abierto en el mundo.
Vamos a describir la parte que nos interesa, Node.js es un programa, V8 es un motor de javascript de código abierto creado por Google, por lo que también lo hace un programa, V8 está escrito en C++, y la tarea de V8 es tomar código Javascript y convertirlo a código máquina (compilar código), pero lo que lo hace especial para nuestros fines es que puede ser embebido dentro de otros programas, lo que permite que V8 esté embebido en Node.js, V8 por así decirlo es el punto de partida para toda la funcionalidad de Node.js.
Node.js también está escrito en C++ y utiliza la API de V8 para agregarle características y funcionalidades nuevas a Javascript. Estas nuevas funcionalidades permiten tener acceso al sistema de archivos y carpetas, nos permite crear un servidor TCP y http, además de acceso a POSIX, o sea, a toda la funcionalidad del sistema operativo donde se encuentre instalado.
Node.js proporciona la sintaxis Javascript para crear programas que tengan acceso a las características del sistema operativo donde sé está ejecutando.
Con esto podemos razonar que con Node.js podemos crear un servidor web, para crearlo, vamos a utilizar NPM (Node Package Manager) y express.js un frawework web.
Vamos a crear una nueva carpeta llamada mi-servidor-web, luego accede a esta carpeta con:
$ cd mi-servidor-web
Ahora vamos a iniciar el proyecto utilizando el siguiente comando:
$ npm init
La línea de comandos nos pedirá algunos datos, puedes dar “enter” a todos si quieres, te muestro un ejemplo:
Press ^C at any time to quit.packagename: (mi-servidor-web)version: (1.0.0)description: Mi primer servidor webentry point: (index.js)test command:git repository:keywords:author: Jaime Cervantes<jaime.cervantes.ve@gmail.com>license: (ISC)About to write to /home/jaime/develop/node.js/mi-servidor-web/package.json:{"name": "mi-servidor-web","version": "1.0.0","description": "Mi primer servidor web","main": "server.js","scripts": {"test":"echo \"Error: no test specified\" && exit 1"},"author": "Jaime Cervantes <jaime.cervantes.ve@gmail.com>","license": "ISC"}Is this ok? (yes)
npm init nos genera un archivo package.json:
{"name": "mi-servidor-web","version": "1.0.0","description": "Mi primer servidor web","main": "server.js","scripts": {"test":"echo \"Error: no test specified\" && exit 1"},"author": "Jaime Cervantes <jaime.cervantes.ve@gmail.com>","license": "ISC"}
Este archivo contiene la información anteriormente proporcionada y además se encarga de controlar los paquetes que instalamos para nuestro proyecto. Por ejemplo, para poder crear nuestro servidor rápidamente, vamos a instalar un paquete llamado express.js de la siguiente manera:
$ npm install express --save
Este comando instala express.js y además actualiza nuestro archivo package.json gracias al parámetro --save en la propiedad dependencies:
{"name": "mi-servidor-web","version": "1.0.0","description": "Mi primer servidor web","main": "server.js","scripts": {"test":"echo \"Error: no test specified\" && exit 1"},"author": "Jaime Cervantes <jaime.cervantes.ve@gmail.com>","license": "ISC","dependencies": {"express":"^4.16.2"}}
Ya teniendo express instalado, vamos a crear nuestro servidor web creando el archivo ./mi-servidor-web/index.js:
const express =require('express');const app =express();app.use(express.static(__dirname +'/public/'));app.listen('3000',function(){console.log('Servidor web escuchando en el puerto 3000');});
Hay una parte importante que nos permitirá ver el funcionamiento de nuestro servidor web:
app.use(express.static(__dirname +'/public/'));
Esta línea le indica a nuestro servidor que cuando un usuario haga una petición de archivos estáticos, por ejemplo, http://localhost:300/index.html enviará como respuesta el contenido de ./public/index.html.
Vamos a crear la carpeta public y el archivo index.html con el editor de nuestro preferencia o en línea de comandos si lo deseas. Agregamos el siguiente contenido a nuestro archivo index.html:
<!DOCTYPE html><htmllang="es"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Mi primer servidor web</title></head><body><h1>HOLA, mi primer servidor web</h1></body></html>
Para ejecutar nuestro servidor nos posicionamos dentro de nuestra carpeta mi-servidor-web y ejecutamos el siguiente comando:
$ node index.js;
Veremos este mensaje en nuestra consola:
Servidor web escuchando en el puerto 3000
Por último abrimos nuestro navegador web y obtendremos nuestro index.html como la imagen de abajo, utilizando http://localhost:3000 o http://localhost:3000/index.html:
En Javascript se utiliza mucho los Patrones Glob, estos permiten seleccionar un número de archivos con una sintaxis parecida a las expresiones regulares pero con menos caracteres comodín y menor complejidad. Un carácter comodín es él * o el signo de ?.
Una gran diferencia entre expresiones regulares es que los patrones glob se ocupan para buscar y filtrar archivos, y las expresiones regulares funcionan con cadenas de texto.
Expresiones regulares trabaja con Texto
Glob trabaja con archivos/directorios
¿Origen?
Tienen su origen en las primeras versiones de Unix, durante la versión 6 para ser exacto, 1969-1975, era el programa /etc/glob, se usaba para seleccionar un conjunto de archivos en la shell, se llamaba glob por ser la abreviación de global.
¿Cómo se usan?
También la funcionalidad es un poco diferente, por ejemplo el * en una expresión regular coincide con la ausencia o cualquier número de repeticiones del carácterque le precede.
Por ejemplo a*, indica cero o más repeticiones de la letra a.
Otro ejemplo con carácter especial es .* el cual indica cero o más repeticiones de cualquier carácter, excepto carácter de nueva de línea.
Por otro lado el carácter * en un patrón glob representa la ausencia o cualquier número de repeticiones de cualquier carácter.
El ejemplo más utilizado es cuando queremos listar archivos con una determinada extensión, por ejemplo:
/$ ls *.js
Muy similar con lo que hacemos en el MS-DOS de windows:
C:\> dir *.js
No es necesario un carácter antes del *, como sucede con las expresiones regulares.
Aquí abajo un ejemplo donde tenemos un archivo de configuración de jasmine, Jasmine es un framework para realizar pruebas a tu código Javascript, este archivo de configuración permite seleccionar que archivos se usarán para ejecutar las pruebas:
Algo que destacar es el uso de doble asterisco **, esto indica cero o más coincidencias de cualquier subdirectorio y archivo, es decir, cero o más niveles de subdirectorios.
También podemos notar el uso de [sS], esta notación de glob indica que acepta sola una vez ya sea la sminúscula o la Smayúscula.
En el arreglo spec_files, su primer elemento es un patrón glob, el cual busca en cualquier subdirectorio relativo al directorio definido en spec_diry que tenga cualquier conjunto de letras al principio pero que terminen con spec.jso Spec.js. Esta definición podría obtener las siguientes rutas de archivos:
/spec/nombre-archivo.spec.js
/spec/nombre-archivo.Spec.js
/spec/helpers/nombre-archivo.spec.js
/spec/helpers/nombre-archivo.Spec.js
/spec/esto/es/otra/ruta/nombre-archivo.spec.js
/spec/esto/es/otra/ruta/nombre-archivo.Spec.js
¿Dónde se utilizan?
En Javascript se utiliza:
Para automatizar tareas que necesitan de la lectura y escritura de archivos
Se utiliza mucho en node.js con herramientas como gulp y webpack
Automatizado de pruebas
Minificado y concatenado para el código en producción.
El patrón de diseño iterador o iterator proporciona una manera de acceder a elementos de un objeto (que contiene algún tipo de datos agregados como un arreglo o una lista) secuencialmente sin exponer su estructura interna.
Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.
Este patrón de diseño también es conocido como cursor, si has usado cursores para recorrer los elementos obtenidos de una base de datos, ¿Que crees?, ahí estas utilizando el patrón de diseño iterador.
El objetivo de este patrón es poder recorrer y obtener elementos de un objeto sin necesidad de saber como estos datos están estructurados. Además de que la responsabilidad de recorrer los elementos no está en el objeto sino en el iterador.
Un iterador se compone normalmente de los siguientes métodos:
iterador.hasNext() o iterador.hayMas()
iterador.next() o iterador.siguiente()
iterador.current() o iterador.elementoActual()
iterador.rewind() o iterador.rebobinar(), que nos permite posicionar el “cursor” en el primer elemento.
Aquí te dejo como implementarlo en un navegador web:
Podemos notar que el siguiente elemento está determinado por la función iterador.next() el cual suma 2 a la variable index y es por eso que obtenemos los valores 1,3,5,7,9 del arreglo data.
Al final rebobinamos al inicio de data e imprimimos el elemento actual, es decir 1, con el método iterador.current().
MongoDB es una base de datos basada en documentos en formato BJSON que es la forma binaria de JSON, en MongoShell se puede ver el uso de este patrón de diseño:
En este ejemplo se obtienen los documentos de la colección users que tengan una propiedad type igual a 2, luego se recorren uno por uno a través de los métodos cursor.hasNext()y cursor.next().