Javascript Extremo: cómo salvar algunos bytes en nuestro código

22 Jun 2011

Introduccion

Hay ocasiones en las que por diversos motivos, tenemos que reducir al máximo nuestros scripts. Puede tratarse por ejemplo de una competición del tipo JS1k donde nuestros códigos tienen que ocupar menos de esos insignifacntes 1024 bites; también puede tratarse de un último esfuerzo por aligerar los tiempos de carga al máximo en un entorno crítico…

Durante este tipo de prácticas, la refactorización es esencial y durante la misma, podemos tratar de implementar algunas de las soluciones más radicales en ahorro de espacio que veremos a continuación. Cabe decir que estas técnicas no son demasiado recomendables en el mundo real por esa capa de dificultad extra que añaden al código tanto a nivel de legibilidad como mantenimiento, pero no dejan de ser curiosidades de este lenguaje a las que podemos recurrir cuando el único objetivo es obtener el código más corto posible…

Pasemos a ver algunas de estas técnicas.

Usar placeholders para la declaración de variables

Siempre nos recuerdan que las variables en Javascript deben iniciarse con var para evitar así su creación en el ámbito global. Esta práctica es quizá de las más extendidas, pero a cada declaración, consumimos los bytes correspondientes de dicho término.

var foo = 1;
var bar = 2;

Una solución inmediata es utilizar la sintaxis de creación secuencial de variables separando sus claves con comas:

var foo = 1, 
    bar = 2;

Pero podemos exprimir aún más las capacidades del lenguaje cuando trabajamos dentro de funciones/métodos: como los argumentos de éstos pasan a ser variables locales dentro de su ámbito, es posible omitir su declaración bastando solo con nombrarlas.

function( foo, bar ){
  foo = 1; // Instead var foo = 1 
  bar = 2; // Instead var bar = 2
}

Cadena de asignaciones

Ya que en Javascript una asignación devuelve el valor asignado, podemos encadenar sucesos para ahorrar espacio.

// Normal
c = a + b; if( c < 10 ){ //... }
 
// Byte-safe
if( ( c = a + b ) < 10 ){ //... }

Esta técnica es especialmente interesante cuando estamos trabajando con métodos; un gran ejemplo lo tenemos con la función JSONP de @jed:

a = this.localStorage; if( a ){...} // before
if( a = this.localStorage ){...}   // after

Almacenar variables en arrays para su manejo

Esta técnica es más compleja porque requiere tener mucho cuidado sobre el código que se está ejecutando. La idea es almacenar todas las variables en un array para utilizarlo como un placeholder que evite declaraciones intermedias:

var a = 1, b = 2, c ; c = a; a = b; b = c // before
var a = 1, b = 2; a = [b, b = a][0]  // after

Usando los ínidices de los arrays para iterar en objetos

Este caso también requiere que conozcamos el código y la naturaleza de los datos con precisión. Consiste en utilizar los índices de un array y la estructura de un bucle para operar siempre que sepamos con certeza de que sus valores son verdaderos (truthy):

for( a= [1, 2, 3, 4, 5], l = a.length, i = 0; i < l; i++ ){ b = a[i];...} // Before
for( a = [1, 2, 3, 4, 5], i = 0; b = a[i++]; ){...} // After

NOTA: Para un análisis de los tipo de datos (falsy y truthy) en Javascript, se recomienda el artículo: «Falsy Values en Jascript«.

Operadores Unarios

Los operadores unarios permiten ahorrar bytes provocando coerciones en los tipos de datos. Esto resulta especialmente útil cuando queremos obtener el booleano de una operación para continuar operando.

Por ejemplo, para detectar si una cadena cumple con un determinado patrón, solemos usar indexOf:

var myString = 'Hello World';
if( myString.indexOf('hello') >= 0 ){ //... } // Before
if( ~myString.indexof('hello') ){ //... } // After

También podemos convertir entre tipos usando sus correspondientes operadores:

var foo = "1";
var bar = parseint( foo, 10); // Before
var bar = +foo; // After

NOTA: Para una completa revisión de los operadores unarios y la coerción de tipos, se puede ojear el artículo «Coerción de datos en Javascript«.

Usando el poco conocido método .link

El objeto String tiene en Javascript un método .link para la creación de enlaces HTML:

html = "<a href="" + url + "">" + text + "</a>" // before
html = text.link(url) // after

Funciones autoejecutables

Ya hablamos aquí hace algún tiempo del importante papel de las funciones autoejecutables en Javascript. Si tratamos de ahorrar algunos bytes en su definición, podemos usar alguno de los siguientes dos métodos:

;(function(){...})() // before
new function(){...}  // after, if you plan on returning an object and can use `this`
!function(){...}()   // after, if you don't need to return anything

Conclusión

Como hemos podido ver a lo largo de los métodos descritos, es posible reescribir muchas veces la lógica de nuestra aplicación para ganar algunos bytes. Sin embargo, el coste de esto es volver el código ilegible y hacerlo difícil de mantener.

Se trata en definitiva de técnicas a las que solo deberíamos recurrir en circunstancias extremas.

Más información

Existe en Github un repositorio con algunas técnicas más catalogadas como extrema:
Byte-saving Techniques.

Más:

{4} Comentarios.

  1. Madman

    La verdad es que si. Debe ser un entorno muy extremo para que todo el código se pique de esa guisa.
    Me he encontrado con códigos muy similares escritos de tal forma por puro frikismo, sin necesidad y sin fin alguno; hay gente que piensa que escribiendo así el código los hace mejores o más entendidos… en fin en fin, las cosas que se ven…

    Aún así. es interesante ver como puedes darle una vuelta de tuerca al código para que ocupe menos, sobretodo, con código que se usa cotidianamente y que, quizás, no te has planteado simplificarlo así nunca.

    Un saludo y gracias por otro de tus post!!

  2. yeikos

    Utilizo mucho de los métodos anteriores pero algunos son simplemente estúpidos. Alomejor ahorras 5 caracteres pero lo haces menos comprensible y en algunos casos hasta menos óptimo.

    Reutilizar variables puede ser otro método para ahorrar no solo algunos bytes si no memoria, pero siempre con cuidado ya que puede dificultar la lectura del código al no estar relacionado el nombre de la variable con la tarea que desempeña.

  3. Uziel

    Hola… me parecen muy interesante tu blog… lo acabo de descubri =P .

    Tengo una duda, no va con el tema pero tal vez me puedas ayudar a comprender esto:

    a= []
    b= [1,2]

    console.debug(!!a);
    console.debug(!!b);

    console.debug(a<1);
    console.debug(b<1);

    Mi pregunta es porque si tanto a como b son true, en las a<1 es true , y en b<1 es false. Como es que se evaluan ??

    Gracias por tu atención. Saludos!

    • Carlos Benítez

      Hola;
      es un tema de coerción de tipos.

      Es fácil de comprobar si revisas la secuencia con la que Javascript evalúa los términos de forma interna.
      Cuando creas un array utilizando la notación literal (var a = []; ), al estar ‘vacío’, se considera un falsy value:

      var a = [];
      console.log( a === 0 ); // false
      console.log( a == 0 ); // true
      

      Cuando realizamos una igualdad no estricta (==), los valores falsy equivalen a 0, de ahí que sea true. Y se cumple tu condición: a < 0. Cuando el array contiene elementos, deja de ser falsy:

      var a = [ 1, 2 ];
      console.log( a === 0 ); // false
      console.log( a == 0 ); // false

      Y por tanto, no se cumple la condición a < 0. De ahí el resultado de tu ejemplo. Saludos!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *