Javascript. 6 consejos para mejorar el rendimiento del código que escribes
Lista de consejos fáciles de implementar en el día a día que te ayudarán a escribir código en Javascript más optimizado
English version: https://latteandcode.medium.com/javascript-6-tips-to-improve-the-performance-of-the-code-you-write-dfe6119fbf3
Si has llegado hasta aquí estoy seguro de que ya tienes algo de práctica desarrollando con Javascript. Como habrás comprobado, es un lenguaje muy abierto: Javascript nos impone muy pocas restricciones para trabajar con él. Por eso creo que de vez en cuando resulta muy interesante parar, coger aire, y repasar algunas recomendaciones muy fáciles de seguir para escribir código mucho más optimizado.
He recopilado algunas de las técnicas que más uso en este artículo. Espero que te resulten útiles y por supuesto, estaré encantado de leer en los comentarios las tuyas. Como siempre digo, se trata de seguir aprendiendo entre todos.
¡Vamos allá!
Calcula una única vez
Muchas veces no prestamos atención a las veces que obligamos a Javascript a reservar memoria para el mismo objeto o a realizar el mismo cálculo pesado:
function calculateSomethingHeavy() {
...
}function foo(bar) {
const result = calculateSomethingHeavy();
// do something with result and bar
}foo(1);
foo(2);
Aquí por ejemplo cada vez que invocamos la función foo
estamos invocando a la función calculateSomethingHeavy
aunque no depende de los argumentos.
Otro ejemplo más sencillo que ilustra bien este caso es:
function foo(bar) {
const object = { key: 'value'};
// do something with bar
}foo(1);
Aquí cada vez que invocamos foo
estamos creando el mismo objeto object
una y otra vez, con el consecuente gasto de memoria.
La solución a este problema es usar “closures” que permitan recordar esos valores de modo que no nos veamos obligados a recalcularlos cada vez:
function calculateSomethingHeavy() {
...
}function fooCreator() {
const result = calculateSomethingHeavy();
return foo(bar) {
// do something with result and bar
}
}const foo = fooCreator();foo(1);
foo(2);
O incluso emplear el patrón módulo:
const module = (function foo(bar) {
const object = { key: 'value'};
// do something with bar
return {
doSomething: function(bar) {
// do something with bar
}
}
})();module.doSomething(1);
Usa métodos nativos
Muchas veces tendemos a escribir funciones que realizan cosas que el propio Javascript ya tiene implementado en forma de funciones nativas del lenguaje.
El caso más habitual suele ser el de realizar operaciones sobre un array de elementos al que le aplicamos una función: igual te sorprende ver el trozo de código siguiente pero te prometo que es muy habitual:
function applyFunctionToArray(arr, fn) {
const newArray = [];
for (let i = 0; i < arr.length; i ++) {
const result = fn(arr[i]);
newArray.push(result);
}
return newArray;
}
Si prestas atención te darás cuenta de que es una implementación muy genérica del método Array.prototype.map
que nos permite aplicar una función a los elementos de un array y generar uno nuevo con los resultados.
Ese trozo de código, que como te digo no es tan difícil encontrárnoslo por el camino, tiene un rendimiento mucho peor que trabajar con map
directamente. Dar a conocer este tipo de funciones que implementa Javascript de forma nativa es uno de los motivos por los que cada miércoles comparto una “receta javascriptera”: a menudo reinventamos la rueda sin saber que ya Javascript nos da lo que queremos.
La palabra delete
La palabra delete
podemos emplearla para eliminar una clave de un objeto. Sin embargo tiene una contrapartida: cuando la empleamos el motor V8 comienza a tratar ese objeto como si fuera un objeto “plano” eliminando una serie de optimizaciones que realiza por debajo.
Por tanto la recomendación es que siempre que podamos asignemos undefined
a la clave que queramos eliminar de modo que el rendimiento del objeto en memoria no se vea afectado:
const obj = { 'foo': 1, 'bar': 2, 'zeta': 3};
obj.foo = undefined;
La otra alternativa es emplear el operador “rest” para eliminar la clave que no queramos, sin embargo esta forma sí es más lenta que el propio delete
por lo que no merece la pena:
const obj = { 'foo': 1, 'bar': 2, 'zeta': 3};
const {foo, ...objectWithoutFoo} = obj;
Divide el código
Cuando creamos aplicaciones web una de las cosas más importantes a las que tenemos que prestar atención es conseguir que el código se cargue lo más rápido posible de cara a que el usuario visualice “algo” lo más rápido posible.
En esto influye mucho el tamaño de los archivos JS que el navegador tiene que descargar y lo optimizado que se encuentre el código de la aplicación.
Gracias a la magia de Webpack y otros “empaquetadores” de código resulta muy sencillo dividir la aplicación en diferentes trozos que el navegador vaya requiriendo a medida que el usuario navega por la web sin necesidad de descargar “toda la aplicación”.
Pero los beneficios de Webpack no terminan ahí, también es capaz de realizar una operación llamada “tree shaking” para eliminar dependencias que no estemos usando y que a menudo importamos sin darnos cuenta:
Mi consejo es que te familiarices con estos conceptos porque ayudan a tener aplicaciones mucho más ligeras y rápidas.
No todo tienen que ser objetos planos
Desde hace unos años Javascript tiene una serie de objetos especiales que están optimizados para determinados tipos de tareas.
Por ejemplo, los “Sets” nos permiten tener arrays sin duplicados y los Maps y los Weak Maps ofrecen interesantes beneficios a la hora de trabajar con objetos del tipo clave-valor:
Por eso quiero animarte a que de vez en cuando profundices un poco más en este tipo de objetos que proporciona Javascript de forma nativa, ya que muchas veces nos permitirán optimizar determinadas piezas de nuestro código.
Para muestra un botón. Gracias a los “Sets” eliminar duplicados de un array es tan sencillo como esto:
const arr = [1, 2, 1, 2, 3, 4];
const arrayWithoutDuplicates = [...new Set(arr)];
Y para terminar… ¡cuidado con las librerías!
Existe una serie de librerías que habitualmente se instalan al comienzo de los proyectos y que sin embargo suelen ser bastante “pesadas”, lo cual provoca que los archivos finales de nuestra aplicación alcancen un gran tamaño.
Ejemplos de este tipo de librerías son Lodash o MomentJS. En el caso de la primera muchas veces la traemos para realizar operaciones que ya Javascript puede realizar de forma nativa.
En el caso de la segunda directamente sus creadores recomiendan emplear alternativas mucho más optimizadas, como uno de mis recientes descubrimientos: la librería Date-FNS.
Conclusiones
Como puedes ver todos estos consejos son muy fáciles de implementar en nuestras aplicaciones y siguiendo el dicho de que “todo suma” nos ayudarán a tener aplicaciones mucho más optimizadas y rápidas.
Además, si tenéis curiosidad por comparar lo rápido que funcionan distintas implementaciones de la misma lógica podéis consultar la herramienta JSBench.me:
¿Quieres recibir más artículos como este?
Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇