Más allá del misterio: Asignando valores con el Revealing Module Pattern

Un patrón bastante extendido cuando desarrollamos código en JavaScript es el Revealing Module Pattern para poder establecer ámbito sobre ciertas partes de nuestro módulo. Quien más quien menos que desarrolle en JavaScript (y no esté en una cueva) ha oído hablar de él así que no vamos a hacer un análisis en detalle sobre el mismo.

Por otro lado, es bastante común exponer un objeto con los nombre de las propiedades que coincidentes con los métodos y propiedades que exponemos y aquí viene el punto propenso a errores que quiero mitigar con este post.

Imaginemos este código:

   1:  var Person = function () {
   2:      var name,
   3:          sayHello = function () {
   4:              alert('Hi, my name is ' + name);
   5:          };
   6:      return {
   7:          name: name,
   8:          sayHello: sayHello,
   9:      }
  10:  }
  11:   
  12:  var andoni = new Person();
  13:  andoni.name = 'andoni';
  14:  andoni.sayHello();

Si yo os pregunto que mostrará la función sayHello por pantalla, que me diríais?

Pues aunque no os lo creáis un bonito mensaje: "Hi, my name is undefined"

Ahora es cuando viene lo de:

“¿Pero que ha pasado? Si yo he asignado el name a "Andoni"!!!

Esto seguro que es por lo del this, o lo del lexical scope … JavaScript es un mierda y que porque no nos habremos quedado en la era de COBOL…”

Que no cunda el pánico, vamos a entender que ha pasado antes de pegarle fuego a V8, Chakra y a todo lo que se mueva…

Tened en cuenta que si aplicamos el patrón, la función nos devuelve otro objeto con dos propiedades (name y sayHello). Cada una de ellas apunta a los objetos reales que está revelando. El código que hemos visto arriba recibe ese objeto y lo que está haciendo es cambiar a que apunta la propiedad name del objeto revelado, que obviamente, no es lo que esperábamos… (recordad los punteros)

Revisemos nuestro código problemático con una modificación:

   1:  var Person = function () { 
   2:      var name, 
   3:          setName = function(nameToSet){ 
   4:              name = nameToSet; 
   5:          }, 
   6:          sayHello = function () { 
   7:              alert('Hi, my name is ' + name); 
   8:          }; 
   9:      return { 
  10:          name: name, 
  11:          setName: setName, 
  12:          sayHello: sayHello, 
  13:      } 
  14:  }
  15:   
  16:  var andoni = new Person(); 
  17:  andoni.setName('andoni'); 
  18:  andoni.sayHello();

Ahora si que recibiremos el mensaje esperado, debido a que la modificación se esta realizando sobre el mismo objeto y no sobre la propiedad del proxy que nos está siendo revelado.

Es un problema sutil y en ocasiones difícil de corregir así que espero que les sea de utilidad.

Un saludo desde Seattle!!

Andoni Arroyo

5 comentarios sobre “Más allá del misterio: Asignando valores con el Revealing Module Pattern”

  1. Queda un poco raro en el objeto que devuelves acceder a name como una propiedad y tener que usar una función para modificarlo.

    ¿No sería más claro exponer sólo hacia el exterior un getName()/setName(), al estilo Java?

  2. Typo corregido, gracias Crowley.
    Juanma, yo también opino que queda más claro. El segundo ejemplo solo modifica el primero para demostrar el punto del que hablo (estamos modificando el objeto que devolvemos, no el original) luego cada uno que le dé su toque 😉

  3. Buenas!
    Un par de comentarios para complementar tu post:

    Antes que nada deberías quitar el «new» de la llamada a Person(). No deberías usar «new» en funciones que no son constructoras y la tuya no lo es puesto que no asignas nada a this y encima devuelves algo. Sin usar «new» tu ejemplo funciona igual 🙂

    Dicho esto, si realmente pretendes una función constructora, no es necesario usar el revealing module pattern para nada ya que todo aquello que no coloques en «this» pasa a ser «privado»:

    var Person = function () {
    this.name=»»;
    var privatefunc = function(){
    console.log(«privatefunc»)
    };
    this.sayHello = function () {
    privatefunc();
    console.log(‘Hi, my name is ‘ + this.name);
    };

    }

    var andoni = new Person();
    andoni.name=»andoni»;
    andoni.sayHello();

    En este ejemplo andoni.privatefunc() dará un error, ya que privatefunc() no está asignada a this. Pero por otro lado desde sayHello() si que se puede llamar.

    Eso sí, la función privatefunc al no estar asignada a this, se trata como una función «global», en el sentido de que es hija del objeto global (en los navegadores window). Por lo tanto this, dentro de privatefunc tiene el valor de Window. En este punto se comportaría como una función privada estática (sin acceso al propio objeto).
    Si queremos que privatefunc() tenga acceso al propio objeto, debemos simplemente «salvar this». Es decir asignar this a otra variable (la convención es usar el nombre self) y usar self desde dentro de privatefunc:

    var Person = function () {
    var self = this;
    this.name=»»;
    var privatefunc = function(){
    console.log(«privatefunc of » + self.name)
    };
    this.sayHello = function () {
    privatefunc();
    console.log(‘Hi, my name is ‘ + this.name);
    };

    }

    var andoni = new Person();
    andoni.name=»andoni»;
    andoni.sayHello();

    Con esta modificación, privatefunc() tiene acceso al propio objeto a través de self, por lo que ahora sí, es el equivalente a una función privada.

    ¿Y el revealing module pattern? El revealing module pattern se suele usar en aquellos casos en que no nos interesa una función constructora (en estos casos no podemos hacer algo «privado» simplemente «no asignándolo a this»).

    Pero… podemos exponer propiedades de lectura/escritura a través del revealing module pattern?
    Pues sí, no HAY NINGUNA NECESIDAD de exponer una función de escritura. Aquí tienes el código, usando el revealing module pattern:

    var Person = function () {

    var name;
    var sayHello = function () {
    console.log(‘Hi, my name is ‘ + this.name);
    };

    return {
    name: name,
    sayHello: sayHello
    };
    };

    var andoni = Person();
    andoni.name=»andoni»;
    andoni.sayHello();

    Fíjate que la única diferencia es que la función sayHello() usa this.name en lugar de name. El valor de this dentro de la función sayHello es el propio objeto de la cual esta función es miembro… Eso es el objeto anónimo devuelto por la función Person.

    Saludos!

  4. Hola Eduard:

    En el post quería dejar claro que estamos devolviendo otro objeto, no el resultado predeterminado de invocar la function con el new (el this) y por eso el comportamiento comentado.

    Con todo lo que comentas 100% de acuerdo, muchas gracias por tu aportación, muy interesante!

Responder a gulnor Cancelar respuesta

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