10 conceptos para mejorar tu dominio de JavaScript

Jan 13 2023
JavaScript es indiscutiblemente uno de los lenguajes de programación más populares del mundo y se utiliza para crear sitios web interactivos y aplicaciones web. Para los desarrolladores, aprender JavaScript puede generar una gran cantidad de oportunidades debido a su fortaleza y versatilidad.
Foto de Gabriel Heinzer en Unsplash

JavaScript es indiscutiblemente uno de los lenguajes de programación más populares del mundo y se utiliza para crear sitios web interactivos y aplicaciones web. Para los desarrolladores, aprender JavaScript puede generar una gran cantidad de oportunidades debido a su fortaleza y versatilidad. En este artículo, exploraremos 10 conceptos clave que pueden ayudarlo a mejorar su dominio de JavaScript y llevar sus habilidades al siguiente nivel. Estos conceptos le ofrecerán una base sólida en el idioma y lo ayudarán a convertirse en un desarrollador más competente y seguro.

1.Promesas

Una poderosa herramienta para manejar acciones asincrónicas en su código son las promesas de JavaScript. Le brindan un medio para lidiar con los resultados de una operación asíncrona, como una solicitud de red, sin bloquear la ejecución del resto de su código. Una promesa puede estar en uno de tres estados: cumplida, rechazada o pendiente, y simboliza el resultado final de una operación asíncrona.

En comparación con el empleo de funciones de devolución de llamada, las promesas ofrecen un mecanismo para manejar los resultados de una acción asincrónica de una manera más optimizada y controlada. Además, facilitan el manejo de errores de manera consistente y encadenan varias tareas asincrónicas.

Las promesas se usan ampliamente en bibliotecas y marcos de JavaScript, como AngularJS, ReactJS y Node.js. Introducidos en ES6, son un concepto fundamental para el desarrollo de JavaScript moderno. La legibilidad, la capacidad de mantenimiento y el rendimiento de su código pueden mejorarse considerablemente si sabe cómo usar las promesas correctamente.

Al usar promesas de JavaScript, el objeto " promesa " es un constructor que crea una nueva promesa y toma un solo argumento, que es una función llamada "ejecutor". La función ejecutora toma dos parámetros, " resolver " y " rechazar ", que se pueden usar para indicar si la operación asincrónica ha tenido éxito o no.

Veamos el siguiente ejemplo:

Una vez que se crea una promesa, puede usar su método " entonces" para adjuntar devoluciones de llamada que se invocarán cuando se resuelva la promesa, y su método " capturar" para adjuntar devoluciones de llamada que se invocarán cuando se rechace la promesa. Alternativamente, puede administrar varias promesas simultáneamente usando los métodos Promise.all() y Promise.race(). Las promesas están disponibles en todos los entornos modernos de JavaScript y son compatibles con todos los navegadores principales, por lo que puede usarlas con confianza en sus proyectos.

2. Asíncrono/Espera

Al igual que las promesas, la característica " async/await " se usa en JavaScript para manejar la operación asincrónica. Se introdujo en ECMAScript 2017 (ES8) y se basó en las promesas de hacer que el código asíncrono en JavaScript sea más manejable y fácil de leer. Async/await ofrece una técnica para crear código asíncrono con apariencia y comportamiento síncronos.

Una función asíncrona se define mediante la palabra clave " async ". La función asíncrona puede generar una promesa y usar la palabra clave " esperar " para retrasar la ejecución del código mientras espera que se cumpla la promesa.

A continuación se muestra un ejemplo:

En el código anterior, " fetch " es una función asíncrona que devuelve una promesa y la palabra clave " esperar " se usa para esperar a que se resuelva la promesa antes de analizar los datos JSON.

El uso de async/await hace que su código sea más fácil de comprender, mantener y ser más sólido. Y en comparación con las devoluciones de llamada y las promesas, es una forma más moderna, clara y efectiva de escribir código asíncrono.

Dicho esto, es crucial tener en cuenta que async/await complementa las promesas, no las reemplaza. En pocas palabras, es una sintaxis diferente para realizar la misma operación. Todavía se basa en promesas para manejar el flujo asíncrono internamente.

3. Cierres

En JavaScript, las funciones internas pueden acceder a las variables y al alcance de sus funciones externas gracias a un concepto clave llamado cierres. Cuando una función se define dentro de otra función y la función interna conserva el acceso a las variables y el ámbito de ejecución de la función externa incluso después de que la función externa haya completado su ejecución, se genera un cierre.

Considere el siguiente código:

En este ejemplo, la función interna puede acceder a la variable x desde el ámbito de la función externa porque está definida dentro de la función externa . La función externa devuelve la función interna, que luego se asigna a la variable myClosure. La función interna aún tiene acceso al valor de x y puede devolverlo cuando se la llama, incluso cuando la ejecución de la función externa está completa.

Los cierres en JavaScript se usan comúnmente para una variedad de tareas, incluida la creación de variables privadas, la aplicación de paradigmas de programación funcional y la creación de cierres en bucles. Al permitir que las funciones lleven datos con ellas, los cierres también se pueden utilizar para crear código robusto y expresivo. También en JavaScript, se puede usar para implementar patrones de programación orientados a objetos.

No obstante, los cierres también pueden provocar fugas de memoria si no se usan correctamente, ya que la función interna continúa manteniendo una referencia a la variable y el alcance de la función externa y el recolector de elementos no utilizados no puede liberar la memoria.

4. Herencia prototípica

A diferencia del modelo de herencia basado en clases que se usa en lenguajes como Java y C#, JavaScript usa un modelo de herencia basado en prototipos. En JavaScript, los objetos heredan sus propiedades y métodos de otros objetos en lugar de clases.

Cada objeto de JavaScript tiene una propiedad interna llamada __proto__ (o [[Prototipo]]), que apunta a un objeto diferente conocido como "objeto prototipo". Cuando se accede a las propiedades o métodos de un objeto, JavaScript primero determina si el objeto en sí tiene la propiedad o el método definido directamente en él. Si no es así, JavaScript comprueba el objeto prototipo del objeto y sigue buscando en la cadena de prototipos hasta que encuentra la propiedad o el método o llega al final de la cadena.

Veamos el siguiente ejemplo:

Observará en el código anterior que el objeto " perro " en este ejemplo hereda el atributo " come " de " animal ", su objeto prototipo, en lugar de tenerlo especificado directamente en él. Así es como funciona la herencia de prototipos. Es un mecanismo en el que los objetos pueden heredar las propiedades y métodos de otros objetos.

Es esencial tener en cuenta aquí que la herencia en este contexto es dinámica y no estática, lo que significa que puede modificar el objeto prototipo agregando o eliminando propiedades y métodos, y los objetos que se derivan de él se actualizarán para reflejar los cambios.

Aunque el paradigma de herencia basado en prototipos en JavaScript tiene el potencial de ser más flexible y potente que la herencia basada en clases, también puede ser más complicado y difícil de comprender. Para utilizar y crear correctamente objetos en JavaScript, se debe tener un conocimiento sólido de la herencia basada en prototipos.

5. Curry

Currying en JavaScript es una técnica que permite la aplicación parcial de una función rellenando previamente algunos de sus argumentos. Esto permite que las llamadas a funciones se reutilicen y sean flexibles.

Considere una función que acepta los dos argumentos x e y y devuelve su total como ejemplo.

Usando cierres, podemos cambiar esta función en una función curry que acepte el argumento restante.

En esta ilustración, el uso inicial de add(2) produce una nueva función que acepta el argumento final, y. Dado que los valores de x e y ya están establecidos en 2 y 3, respectivamente, cuando se llama a add2(3), el resultado es 5.

Las funciones de flecha y operador de propagación también se pueden utilizar para lograr curry. Echemos un vistazo a un ejemplo a continuación:

En esta ilustración, la primera llamada a add(2) produce una nueva función de flecha que agrega x e y a los argumentos restantes, independientemente de su número.

Currying puede aumentar la reutilización y la legibilidad del código al mismo tiempo que permite que las llamadas a funciones sean más flexibles.

6. Funciones de orden superior

Las funciones de orden superior en JavaScript son aquellas que aceptan una o más funciones como argumentos y/o devuelven otra función como salida. Como pueden usarse como cualquier otra variable o valor, estas funciones también se conocen como "funciones de primera clase". El término "orden superior" indica un mayor nivel de abstracción que la función JavaScript habitual.

Algunos ejemplos de funciones de orden superior son:

i. Array.prototype.map(): esta función aplica una función de devolución de llamada a cada elemento de una matriz que recibe como argumento, devolviendo una nueva matriz con los resultados.

ii. Array.prototype.filter(): esta función acepta una función de devolución de llamada como argumento y la aplica a cada elemento de la matriz, devolviendo una nueva matriz con solo los elementos que pasan la prueba.

iii. Array.prototype.reduce(): esta función toma una función de devolución de llamada como argumento y la aplica a cada elemento de una matriz, acumulando los resultados en un solo valor.

IV. Array.prototype.forEach(): esta función es un método iterativo y llama a una función de devolución de llamada proporcionada una vez para cada elemento de una matriz en orden de índice ascendente.

v. setTimeout(): esta función acepta una función de devolución de llamada como entrada y programa su ejecución para una determinada cantidad de tiempo en el futuro.

vi. Promise.then(): esta función acepta una función de devolución de llamada como entrada y programa su ejecución para cuando se resuelva una promesa.

Estas funciones de orden superior ofrecen un mecanismo para abstraer la lógica y la funcionalidad pasándolas como variables o argumentos, lo que permite un código más adaptable y reutilizable. Por ejemplo, si desea que una matriz de números solo devuelva números pares, puede usar el método de orden superior filter().

La función filter() en este ejemplo recibe una función anónima como devolución de llamada y la usa para determinar si cada elemento de la matriz es par antes de devolver solo los números pares.

7. Generadores

Estos son un tipo especial de función en JavaScript que permite pausas y reinicios de ejecución. Son útiles cuando se trabaja con iteradores y programas asincrónicos, ya que le permiten construir una secuencia de valores a lo largo del tiempo.

Una función generadora se define usando la palabra clave function* y usa la palabra clave yield para pausar la ejecución y devolver un valor. Cuando se invoca la función del generador, produce un objeto iterador que se puede usar para controlar la ejecución del generador.

Suponiendo que queremos crear una función generadora que devuelva el siguiente número en la secuencia de Fibonacci:

En el ejemplo anterior, la palabra clave function* se usa para declarar la función generadora fibonacci(). Utiliza la palabra clave yield para devolver el valor actual de a y actualiza los valores de a y b mediante la asignación de desestructuración. El ciclo while asegura que el generador continuará produciendo nuevos valores indefinidamente.

La función generadora, cuando se invoca, devuelve un objeto iterador que se puede usar para controlar la ejecución del generador. El método next() se usa para reanudar la ejecución y devolver el siguiente valor.

Grandes conjuntos de datos, código asincrónico, iteradores y secuencias infinitas se pueden implementar con la ayuda de generadores. Sin tener que cargar todos los datos en la memoria a la vez, le permiten generar una sucesión de valores a lo largo del tiempo.

8. Mapas débiles y conjuntos débiles

Similar a un mapa en JavaScript, un WeakMap es un tipo de recopilación de datos que le permite almacenar pares clave-valor. La distinción importante es que las claves en un WeakMap tienen referencias débiles, lo que significa que pueden ser recolectadas como basura si no existen otras referencias a ellas. Por lo tanto, WeakMap se puede usar para almacenar datos que no desea mantener en la memoria si ya no está en uso.

Por otro lado, un WeakSet es similar a un Set pero exhibe el mismo comportamiento de referencia débil para sus componentes. Esto significa que si no hay otras referencias a un elemento en un WeakSet, se puede recolectar basura.

Aquí hay un ejemplo del uso de un WeakMap en JavaScript:

En el ejemplo anterior, creamos un WeakMap y almacenamos un par clave-valor con un objeto que sirve como clave. Luego usamos el objeto como clave para obtener el valor. El par clave-valor también se elimina del WeakMap cuando establecemos el objeto en nulo, lo que lo hace adecuado para la recolección de basura.

Sin embargo, a continuación se muestra un ejemplo de un WeakSet:

En este ejemplo, se agregan dos objetos a un WeakSet que se ha creado. Luego, el método has() se usa para determinar si un determinado objeto está presente en el conjunto. El primer objeto se elimina del WeakSet y es elegible para la recolección de elementos no utilizados después de establecerlo en nulo.

Tanto WeakMap como WeakSet son útiles para administrar el uso de la memoria de JavaScript porque le permiten almacenar información sin almacenarla en la memoria de forma permanente.

9. apoderados

Los objetos que sirven como intermediarios entre el código que accede a ellos y los objetos subyacentes que representan se conocen como proxies. Le brindan la capacidad de modificar e interceptar operaciones realizadas en el objeto subyacente, como llamadas a métodos, constructores y acceso a propiedades.

El siguiente código es un ejemplo de cómo se puede usar un proxy para interceptar el acceso a la propiedad:

En este ejemplo, un objeto llamado obj está envuelto en un objeto Proxy. Para manejar el acceso y la asignación de propiedades, definimos dos funciones de "controlador", " obtener " y " establecer ". Podemos interceptar y cambiar el comportamiento del objeto subyacente usando estas funciones. Se llamará a la función " obtener ", se registrará un mensaje y se devolverá el valor de la propiedad " nombre " del objeto original cuando accedamos a la propiedad de nombre del objeto proxy.

Los proxies se pueden utilizar para varios propósitos, tales como:

  • Implementación de accesores de propiedad personalizados
  • Implementación de la invocación de métodos personalizados
  • Implementando constructores personalizados
  • Implementación de un comportamiento personalizado para objetos "virtuales"
  • Implementación de un comportamiento personalizado para objetos existentes
  • Implementación de manejo de errores personalizado
  • Implementación de características de seguridad personalizadas

10. Reflejar API

De manera similar a cómo usaría los operadores y funciones integrados de JavaScript, JavaScript Reflect API ofrece una serie de métodos que le permiten realizar varias acciones en los objetos. La distinción principal es que los operadores y funciones integrados a menudo devuelven el resultado de la operación, mientras que los métodos de la API Reflect normalmente devuelven un valor booleano que indica el éxito o el fracaso de la operación.

Echemos un vistazo al siguiente ejemplo:

En este ejemplo, la propiedad "nombre" en el objeto obj se establece en el valor "Juan" mediante el método Reflect.set(). Cuando la operación tiene éxito, el método devuelve verdadero.

Aquí hay otro ejemplo de cómo usar la API Reflect para determinar si un objeto es extensible o no:

Este ejemplo muestra cómo verificar la extensibilidad de un objeto usando el método Reflect.isExtensible(). Debido a su naturaleza extensible por defecto, inicialmente devuelve verdadero. Después de eso, usamos Object.preventExtensions(obj) para evitar que el objeto se extienda. A continuación, usamos Reflect.isExtensible(obj) una vez más, que devuelve falso para indicar que el objeto ya no es extensible.

La API de Reflect proporciona una serie de métodos, como:

  • Reflejar.obtener()
  • Reflect.set()
  • Reflect.has()
  • Reflect.deleteProperty()
  • Reflect.defineProperty()
  • Reflect.getOwnPropertyDescriptor()
  • Reflect.getPrototypeOf()
  • Reflect.setPrototypeOf()
  • Reflect.preventExtensions()
  • Reflect.esExtensible()
  • Reflejar.aplicar()
  • Reflect.construcción()

En conclusión, dominar JavaScript requiere tener una sólida comprensión de una amplia gama de conceptos y técnicas. Los conceptos discutidos en este artículo, como cierres, prototipos, funciones de orden superior son solo algunos de los muchos que son esenciales para comprender cómo funciona JavaScript y cómo escribir código eficiente y efectivo. Además, comprender cómo usar funciones modernas como Promises, async/await, WeakMap y WeakSet, Proxies y Reflect API, que también se analizan aquí, puede ayudarlo a escribir un código JavaScript más moderno y potente. Al tomarse el tiempo para comprender completamente estos conceptos y cómo funcionan, puede convertirse en un desarrollador de JavaScript más competente y seguro.