Entendiendo conceptos de Javascript: Closures, Duck Typing, Prototypes y References
Publicado 09-10-2015
Últimamente, cuando tengo que hacer cosas con Javascript he utilizado Coffeescript, para que fuera un poco más a la Ruby y tener menos dolores de cabeza. Por otro lado siempre he sentido que cuando hago cosas en Javascript estoy haciendo pequeñas chapuzas que funcionan pero que no me acaban de gustar. En realidad puedo decir que aprendí jQuery, pero no mucho más.
Buena praxis
No es que sea una sensación infundada, porque Javascript se presta mucho a la chapuza y es complicado saber cual es la buena praxis en este lenguaje, básicamente, y por mucho que alguno me lo va a discutir o por mucho que te cuenten, en la práctica no existe.
Pros
Esto tiene sus pros y sus contras, centrémonos en los pros, el lenguaje es tan maleable que permite hace cosas bastante potentes y curiosas, que quizá requerirían de mucho más código en otros lenguajes. Además, para ser interpretado, es bastante rápido, sobre todo con los nuevos motores, y si tenemos más o menos claro que es lo que carga y lo que no (y pocos escrúpulos) podemos conseguir una buena velocidad.
Mi problema
Mi problema fundamentalmente es que no quería leerme veinte libros sectarios sobre patrones y opiniones peregrinas de buena praxis, a mi modo de ver eso será interesante en un ámbito académico. En el mundo real necesitamos hacer las cosas bien pero sin perder productividad, y leer un porrón de libros, seleccionar la religión que más nos guste, es un tiro en toda la línea de flotación del barco de la productividad para aprender un lenguaje que, lo que es la intuición del programador con experiencia previa, se la pasó por la quilla antes de salir al mercado.
Braben al rescate
Así que llamé a mi amigo David González AKA Braben, un tipejo que es capaz de programar videojuegos en un Z80, en un 68000, en C y hasta en Javascript con HTML5 y le pedí ayuda: “Explícame los conceptos básicos y los ámbitos de las variables en menos de una hora, no para tontos, pero sí para vagos”.
En poco rato me envió cinco ficheros que ejecutados con node y leídos los comentarios me han ahorrado un mar de lágrimas.
Los ficheros
Los voy a poner a continuación por si te sirven a ti también, yo creo que sí te pueden servir si estás empezando a descender a los infiernos del Javascript, y por favor, que nadie se ofenda, está muy bien ser un sabio del lenguaje y haberse empapado todos los libros sobre patrones, closures y objetos deconstruidos, esto no va contigo si eres así, todos te admiramos y tal.
closures.js
1var outside = 1 2 3function f() { 4 var inside; 5 6 // Variable outside_num disponible dentro de f porque 7 // f está dentro del closure que almacena la variable 8 // local outside_num. En este caso el fichero es el 9 // closure. 10 console.log('outside is ' + outside.toString()); 11 12 inside = "Yeah, whatever"; 13} 14 15f(); 16 17// Modificamos la variable outside_num, pero el código de f() 18// usa esa variable del mismo closure. 19outside = 2; 20 21// f reinterpreta outside_num, que ahora vale 2. Es decir, 22// no se asignó el valor de outside_num en el momento en 23// el que se creó la función. 24f(); 25 26// Sin embargo la variable inside_str está en otro closure 27// y no podemos verla desde aquí. 28console.log('inside is ' + typeof inside); 29 30console.log('EOF');
duck_typing.js
1// Si grazna como un pato y nada como un pato: Es un pato. 2 3// Esto es una construcción. Es decir, 4// 'one' es un objeto y no una clase. 5one = { 6 hello: function() { 7 console.log("I'm one."); 8 } 9} 10 11two = { 12 hello: function() { 13 console.log("I'm two."); 14 } 15} 16 17// Tanto 'one' como 'two' responden a 'hello'. 18object_list = [one, two] 19object_list.forEach(function(element) { 20 element.hello(); 21}) 22 23// forEach es secuencial, y no vuelve hasta 24// que termine. Lo que puede llevar a confusión 25// es que si lanzamos funciones de jQuery estas 26// volverán de inmediato mientras se ejectan 27// en paralelo. 28var seq = function() { 29 var arr = [1, 2, 3]; 30 arr.forEach(function(elem) { 31 console.log('Seq: ' + elem.toString()); 32 }) 33} 34 35var eof = function() { 36 console.log('EOF'); 37} 38 39seq(); 40// Esta siempre se ejecutará al final. 41eof();
modules.js
1// Se pueden hacer cosas impresionantes con los closures. 2// 3// Esta es una guía imprescindible (mejor que este fichero) 4// http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html 5 6var my_module = (function () { 7 var mod = {}; 8 var inside; 9 10 mod.inside_get = function() { 11 return inside; 12 } 13 14 mod.inside_set = function(val) { 15 inside = val; 16 } 17 18 return mod; 19})() 20 21my_module.inside_set('Hey!'); 22var result = my_module.inside_get(); 23console.log('Inside (getter) is ' + result); 24 25// Esto no funciona porque mod no tiene un 'inside', 26// has de utilizar el getter/setter. 27console.log('Inside (module) is ' + my_module.inside); 28 29console.log('EOF');
prototypes.js
1/** 2 * Hace que child herede de parent 3 * 4 * @param {Object} child Hijo 5 * @param {Object} parent Padre 6 */ 7function inherit(child, parent) 8{ 9 var parentCopy = Object.create(parent.prototype); 10 parentCopy.constructor = child; 11 child.prototype = parentCopy; 12} 13 14// --- Padre --- 15 16function Clase(val) { 17 this.val = val; 18} 19 20// Método 'hello'. Los prototipos funcionan así: Si haces obj.método, el 21// intérprete buscará método en obj. Si no existe busca en obj.prototype 22// y si tampoco existe sigue tirando de la cadena de prototipos. Con 23// esto el sistema se puede volver horriblemente lento cuando tienes 24// hijos que aportan poco y hay que buscar hasta el padre de los padres 25// de nuestros padres. Y los padres de los padres... 26Clase.prototype.hello = function() { 27 return this.val; 28} 29 30obj = new Clase('Hello World!'); 31console.log("Obj.val: " + obj.val); 32 33// --- Hijo --- 34 35function ClaseGrande() { 36 // Con esto llamamos al constructor del padre. El primer 37 // argumento del call será el this para la llamada a Clase, que 38 // es la función de arriba y a la vez el constructor de Clase. 39 // Sí, esto es jodido: las clases en realidad son funciones que 40 // manejas como si fueran objetos. En otras palabras, Clase() y 41 // Clase.prototype son dos expresiones válidas. 42 Clase.call(this, "I'm BIG!"); 43} 44 45inherit(ClaseGrande, Clase); 46 47ClaseGrande.prototype.hello = function() { 48 console.log("Let's go!"); 49 50 // Lo mismo que con el constructor. Le pasamos el this de un 51 // objeto creado con esta clase (ej: obj_grande más abajo) 52 // al hello del prototipo de Clase. 53 return Clase.prototype.hello.call(this); 54} 55 56obj_grande = new ClaseGrande(); 57 58// Llama al hello del ClaseGrande que imprime "Let's go!" y luego 59// llama al hello de Clase, que devuelve val del obj_grande 60var valor = obj_grande.hello(); 61console.log(valor);
references.js
1// Todos los objetos se pasan por referencia _siempre_. 2 3var obj = { 4 uno: 1, 5 dos: 2 6} 7 8var grandote = { 9 ref: obj, 10 f: function() { console.log("hola") } 11} 12 13console.log(grandote); 14 15// Nótese que estoy modificando obj, no grandote 16obj.dos = 222; 17 18// y sin embargo... 19console.log(grandote);