Skip to content

Instantly share code, notes, and snippets.

@Foxandxss
Last active November 6, 2020 17:44
Show Gist options
  • Save Foxandxss/ec12a9b8cc0cfe8fa2ca to your computer and use it in GitHub Desktop.
Save Foxandxss/ec12a9b8cc0cfe8fa2ca to your computer and use it in GitHub Desktop.
Module pattern Javascript (Español)

Scopes en Javascript:

Si haces algo en plan:

var a = 10;

function foo() {
  console.log(a);
}

foo();

¿Qué imprime? 10. Eso es así en Java también.

Siguiente ejemplo:

for (var i = 0; i < 10; i++) {
  // Hacer algo
}

console.log(i);

¿Qué imprime? 10. ¿10? ¿No he creado la variable i dentro de un for? Sí, pero a diferencia de Java, en Javascript lo único que crea un nuevo "scope" (no confundir con los scopes de angular) son las funciones. Eso quiere decir que:

if (true) {
  var i = 10;
}

console.log(i);

También imprimirá 10. ¿Cómo se hace en Javascript para ocultar ciertas variables a otros contextos?

Hemos dicho que lo único que crea un nuevo scope son las funciones. Entonces podemos hacer:

var john = function() {
  var name = "John"
  var age = 10;
  
  function greet() {
    console.log(name + " has " + age + " years.");
  }
}

var j = john();
j.greet();

En este ejemplo, ¿qué veríamos? ¿Veríamos el saludo? No. ¿Por qué? Porque tanto name, age y greet se han creado dentro de la función person y todo lo que creamos dentro no se verá fuera.

Ok, como puedo exponer la función greet nada más? Una solución sería crear un objeto y meter ahí todo lo que quieras devolver:

var john = function() {
  var obj = {};
  
  var name = "John"
  var age = 10;
  
  obj.greet = function() {
    console.log(name + " has " + age + " years.");
  }
  
  return obj;
}

var j = john();
j.greet();

La idea sería más o menos: De esta función, quiero que greet sea público y name junto a age sean privados.

De hecho si intentas algo como:

console.log(j.age);

Te dará undefined.

Una pega que le veo, es tener que hacer ese:

var j = john();

¿No se supone que la función ya es especifica para john? O sea, tenemos que crear la función, invocarla y ya podemos usarla. ¿Es simplificable? Sí:

var john = (function() {
  var obj = {};
  
  var name = "John"
  var age = 10;
  
  obj.greet = function() {
    console.log(name + " has " + age + " years.");
  }
  
  return obj;
})();

john.greet();

Lo que hemos hecho es que la función se ejecute inmediatamente, a esto se le llama IIFE (Immediately invoked function expression).

Con poner los paréntesis al final en plan:

var foo = function() {
}();

es más que suficiente, pero es muy muy sencillo el no darse cuenta de esos paréntesis al final, por lo que se recomienda poner esos extra paréntesis:

var foo = (function() {
})();

Así al leer el código, se verá ese paréntesis al principio lo cual nos dice inmediatamente de que eso es un IIFE.

Bueno, a todo esto se le llama MODULE PATTERN. O sea, el usar una función para crear cierta lógica y devolver sólo la parte pública.

Hay una variación de este patrón, que es mucho más común:

var calc = (function() {
  var fixedNum = 10;
  
  function add(num) {
    return fixedNum + num;
  }
  
  function sub(num) {
    return fixedNum - num;
  }
  
  return {
    addition: add,
    subtraction: sub
  };
})();

console.log(calc.addition(1, 2));
console.log(calc.subtraction(3, 4));

Aquí en vez de crear un objeto donde vamos añadiendo esas cosas que queremos públicas, directamente devolvemos un objeto donde decimos qué partes queremos hacer públicas. En plan: De todo esto que tengo, expón esto y esto.

La sintaxis es:

return {
  nombrePublico: nombrePrivado
};

En el ejemplo, he decidido que el método add y el método sub sea conocido públicamente como addition y subtraction respectivamente. Dicho con otras palabras: De este módulo, deja fixedNum como privado y devuelve add y sub con esos nuevos nombres.

Si no quieres cambiar los nombres, pones el mismo a cada lado y ya está.

A esto se le llama REVEALING MODULE PATTERN.


Algo útil que también podemos hacer con los IIFE es poder pasarle cualquier tipo de parámetro, por ejemplo, si para nuestro ejemplo anterior queremos pasarle un fixedNum en tiempo de ejecución, podemos hacer lo siguiente:

var initNum = 8;

var calc = (function(initNum) {
  var fixedNum = initNum || 10;
  
  function add(num) {
    return fixedNum + num;
  }
  
  function sub(num) {
    return fixedNum - num;
  }
  
  return {
    addition: add,
    subtraction: sub
  };
})(initNum);

console.log(calc.addition(1, 2));
console.log(calc.subtraction(3, 4));

Con:

var fixedNum = initNum || 10;

lo que estamos haciendo sería asignarle el valor de initNum a fixedNum pero en caso de que initNum sea undefined (o cualquier cosa que sea false), se le asignará el valor por defecto 10.


Para los que les interese Angular, este patrón se aplica a sus factorías. El ejemplo anterior se traduciría como:

angular.module('app').factory('calc', function() {
  var fixedNum = 10;
  
  function add(num) {
    return fixedNum + num;
  }
  
  function sub(num) {
    return fixedNum - num;
  }
  
  return {
    addition: add,
    substraction: sub
  };
});

Esa función que le pasamos al factory, será ejecutada por Angular internamente (por eso no usamos un IIFE) y aquí hemos decidido usar el revealing module pattern para exponer solo lo que nos interesa. Se puede usar otras variaciones del patrón si se quiere, pero ésta es mi favorita.


Escrito por Foxandxss

@diegodelfin7
Copy link

Buen articulo , Gracias.

@dealonzo
Copy link

Bro, excelente aporte.

@yojona
Copy link

yojona commented Aug 16, 2018

¿El uso de let y const hace innecesario este patrón?

@Hyperxq
Copy link

Hyperxq commented Oct 19, 2018

Excelente explicación.

@Hyperxq
Copy link

Hyperxq commented Oct 19, 2018

¿El uso de let y const hace innecesario este patrón?

Yo no lo veo asi, ya que la función de let es el delimitar el scope de una variable, pero este patrón es una forma de encapsular y modularizar funcionalidades en javascript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment