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
Buen articulo , Gracias.