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);
Comentarios por Disqus