Patrones para predefinir variables en funciones con JavaScript (y node.js).

Por Daniel R. @sadasant.

Hace unos días, mientras hacía unas prácticas con node.js y publicaba un tutorial, me tocó escribir una función como esta:

function getDB(db){
  var user = 'usuario';
  var pass = 'clave';
  var host = 'localhost';
  var url = 'http://'+user+':'+pass+'@'+host+':5984/'+db;
  return CouchClient(url);
};

Es una función cualquiera, recibe una cadena identificada como la variable db, concatena una serie de cadenas de caracteres y luego pasa el resultado a la función CouchClient. Funciona correctamente, pero el caso es que, tener que declarar 3 variables (user, pass y host) cada vez que se ejecutase la función me parecía un desperdicio de memoria. Quizás digan que eso no importa en JavaScript, pero para aplicaciones que requieren mucho procesamiento (como juegos) quizás sería mejor que esas variables estuviesen ya predefinidas en vez de re-declararse cada vez que se vuelva a ejecutar la función.

Este tutorial explicará varias maneras (patrones de diseño) de hacer esto, esperando encontrar el patrón más cómodo (al menos para mi) para predefinir variables en funciones.

Simplificando el problema.


Crearemos un script llamado fun00.js con el siguiente código:

function fun(it){
  var isFun = ' is fun!';
  return it + isFun;
};

En esencia hace lo mismo, recibe una variable (en este caso it) y la concatena a una cadena (llamada isFun), para luego retornarla.

Preparándonos para probar los ejemplos.


Para probarlos desde el navegador, importamos el código al documento colocando la siguiente línea en el html:

<script src="fun.js"></script>

Y luego podremos colocar:

<script>
  alert(fun('JavaScript'));
</script>

También, para tener una información más detallada, podríamos hacer lo siguiente si tenemos un debugger como Firebug:

<script>
  console.debug(fun('JavaScript'));
</script>

Para probarlos en node.js, tendríamos que añadir la siguiente línea:

console.log(fun('JavaScript'));

Y luego lo ejecutamos con:

$ node fun00.js
JavaScript is fun!

En lo personal prefiero usar esta última para este tipo de ejercicios.

1. Definiendo las variables fuera de la función.


Una de las maneras más sencillas de predefinir variables es declarándolas por fuera de la función, y luego llamándola dentro. Para probarlo crearemos el programa fun01.js y le colocaremos dentro:

var isFun = ' is Fun!';

function fun(it){
  return it + isFun;
};

console.log(fun('JavaScript'));
// JavaScript is fun!

Ventajas:

  • Es intuitivo.

Desventajas:

  • Cada vez que utilizas nombres globales para asignar variables tan simples se muere un gatito. El problema de las variables globales es que limita la extensibilidad del código, así que esto es un anti-patrón.

2. Definiendo las variables dentro de un objeto literal que además tenga la función.


Otra de las maneras de predefinir variables en funciones es colocándolas fuera de la función, pero colocando todo el conjunto de variables y funciones dentro de un objeto literal mayor.

var fun = {
  isFun: ' is Fun!',
  run: function (it){
    return it + this.isFun;
  }
};

console.log(fun.run('JavaScript'));
// JavaScript is fun!

Teniendo node.js, podríamos probarlo así:

$ node fun02.js 
JavaScript is Fun!

Ventajas:

  • Es intuitivo.
  • Utiliza solo una variable global.
  • Permite cambiar las variables predefinidas desde funciones externas.

Desventajas:

  • Como la función que queda se tiene que ejecutar con la sintaxis del punto fun.run() quizás pueda ser considerado como un nivel de abstracción innecesario cuando solo se quiere llamar a una sola función, pero que tenga variables predefinidas.

3. Definiendo las variables dentro de una función anónima que retornará la función que queremos.


Otra de las maneras es utilizando el poder de programación funcional de JavaScript. Igualamos fun a una función anónima que ejecutamos al momento, dentro de ella, declaramos isFun y luego retornamos la función que realmente queremos que esté dentro de fun. Para probar esto, crearemos el script fun03.js con estas líneas de código:

var fun = (function (){
  var isFun = ' is Fun!';
  return function (it) {
    return it + isFun;
  };
})();

Al declarar la variable dentro de la función que ejecutamos al momento, se conservará en ese campo mientras la función que retornamos no desaparezca del tiempo de ejecución (sea eliminada).isFun permanecerá solo en el campo donde fue creada, por lo que solo la podrán detectar elementos que estén en ese campo (como la función que retornamos), por medios externos será imposible cambiar ese valor.

Ventajas:

  • Si te gustan las lambdas (como a mi) te gustará este método.
  • Si quieres variables privadas en JavaScript, ahí tienes como hacerlo.

Desventajas:

  • No es intuitivo para los novatos.
  • Las variables declaradas dentro de la función que se ejecuta al momento no podrán ser modificadas ni leídas por algún otro medio que no sea la función que se retorna, por lo que puede limitar la extensibilidad del programa.

4. Definiendo las variables sólo en la primera ejecución de la función.


Otra de las maneras de predefinir variables en funciones es colocar un condicional dentro de la función que verifique que esa variable está declarada dentro del prototipo; si no lo está (será igual a undefined) la declarará (this.isFun = ' is fun!';), para lo cual creamos un nuevo script llamado fun04.js con el siguiente código:

function fun(it){
  if (this.isFun == undefined) {
    console.log('Setting isFun.');
    this.isFun = ' is fun!';
  }
  return it + this.isFun;
};

Si la variable isFun no está definida en el prototipo, antes de declararla hemos colocado una línea conteniendo console.log('Setting isFun.');, lo que nos permitirá saber cuantas veces se ejecuta esa definición.

Para probarlo correctamente debemos ejecutarla dos veces, por lo que colocamos las siguientes dos líneas al final del código:

console.log(fun('Predefining'));
console.log(fun('JavaScript'));

Al ejecutar el script, en la consola nos mostrará lo siguiente:

$ node fun04.js 
Setting isFun.
Predefining is fun!
JavaScript is fun!

Como vemos, solo definió la variable una vez :)

Ventajas:

  • Es intuitivo.
  • Si se usa para casos puntuales es perfectamente plausible.
  • Da la posibilidad de ejecutar acciones durante la primera ejecución de la función.

Desventajas:

  • El uso excesivo de condicionales puede llegar a ser bastante criticado dentro de la comunidad porque incrementa innecesariamente la complejidad visual del código.

  • Usar demasiados condicionales podría hacer al programa inevitablemente imposible de mantener a largo plazo y mucho menos escalar.

5. Utilizando clases.


Para los fanáticos de la orientación a objetos, otra manera es utilizando clases en JavaScript. Crear una clase es simplemente hacer una función en donde se inicialicen nuevas variables en el prototipo (this.publicIsFun), variables que pueden contener desde datos hasta funciones y otros objetos. Una vez creada, para crear un nuevo objeto, se le asigna a una variable el resultado de new seguido del nombre de la clase, y luego se puede puede acceder a los métodos y propiedades dentro del nuevo objeto (Fun.publicFun()). Para probar esto creamos el nuevo script fun05.js y le colocamos las siguientes líneas:

function Fun (){
  var privateIsFun = ' is closed fun!';
  this.publicIsFun = ' is open fun!';

  this.privateFun = function(it){
    return it + privateIsFun;
  }
  this.publicFun = function(it){
    return it + this.publicIsFun;
  }
};

var Fun = new Fun();

// Probando en node.js
console.log(Fun.privateFun('Using private variables'));
console.log(Fun.publicFun('Using public variables'));

Al ejecutarlo con node.js nos mostraría:

$ node fun05.js 
Using private variables is closed fun!
Using public variables is open fun!

Ventajas:

  • Es intuitivo para quienes sepan sobre clases y objetos.
  • Permite crear muchos más comportamientos que le darían muchísimo más poder al programa.

Desventajas:

  • Usar un excesivo número de clases puede llegar a ser incómodo.
  • A veces esos niveles de abstracción son innecesarios, en especial cuando únicamente se quiere predefinir variables para una función particular.

6. Bind.


Otro método que encontré para hacer este tipo de cosas es utilizando el método bind que tienen todas las funciones en javascript. Tal como nos cuenta la red de documentos de mozilla, bind creará una nueva función con un this igual al parámetro que se le pase a bind, por lo que podemos crear una función anónima que haga lo que queremos que haga y a esa función, antes de asignarla, ejecutar bind, al cual le pasaremos como argumento un objeto literal con las variables pre-definidas que necesitemos. Creamos otro archivo de texto llamado fun06.js con el siguiente contenido:

var fun = function(it) {
  return it + this.isFun;
}.bind({
  isFun: ' is fun!'
});

console.log(fun('JavaScript'));

Y lo probamos de esta manera:

$ node fun06.js 
JavaScript is fun!

Todos los parámetros después del primero que se le pasen a bind serán interpretados como los primeros argumentos de la función, por lo que podríamos escribirlo de esta manera:

var fun = function(isFun, it) {
  return it + isFun;
}.bind(undefined, ' is fun!');

Y nos entregaría lo mismo:

> fun('JavaScript');
'JavaScript is fun!'

Ventajas:

  • Es corto y es intuitivo una vez que entendemos que hace bind.

Desventajas:

  • Las variables predefinidas no se podrán editar.

7. Combinando el tercero con el segundo.


El libro “patrones de diseño para novatos” lo llaman “el patrón revelador” (Detalles en ingles aquí) podemos ver cómo utilizar el segundo ejemplo que hicimos (con objetos literales) mas el tercer método para combinar tanto las variables privadas como las públicas en un nuevo modelo de trabajo. Creamos un último script llamado fun07.js y dentro le colocamos:

var fun = (function (){
  var privateIsFun = ' is closed fun!';

  function privateFun(it){
    return it + privateIsFun;
  }

  function publicFun(it){
    return it + this.publicIsFun;
  }
  return {
    publicIsFun: ' is open fun!',
    privateFun: privateFun,
    publicFun: publicFun
  }
})();

// Probando en node.js
console.log(fun.privateFun('Using private variables'));
console.log(fun.publicFun('Using public variables'));

Al ejecutarlo con node.js nos mostraría:

$ node fun05.js 
Using private variables is closed fun!
Using public variables is open fun!

Ventajas:

  • Permite definir variables y/o funciones públicas y extensibles tanto como hacer que otras sean otras privadas.
  • Es sumamente versátil.

Desventajas:

  • No es intuitivo si no se está acostumbrado a trabajar con objetos literales o con funciones anónimas.
  • No es intuitivo para quienes empiecen a programar JavaScript desde lenguajes como JAVA.

Conclusión.


¿Cuál es más conveniente? Depende de para qué lo vayas a utilizar. Por extensibilidad y sencillez quizás sea mejor el segundo, aunque el que utiliza las clases es más extenso todavía ¿Quizás prefieran el patrón revelador? ¡O bien quieren algo sencillo y hacen bind!

Sean creativos ;)

Gracias por leer este tutorial.

  1. sadasant ha reblogueado esto desde yucazos
  2. yucazos ha publicado esto
Short URL for this post: http://tmblr.co/ZbaP8xAW-b8X
blog comments powered by Disqus