[Reciclaje JavaScript] Encapsulamiento
July 12, 2013
Continuando con la serie de posts sobre como programar bien en JavaScript, en este post vamos a ver el tema de encapsulamiento. Como sabéis, la programación orientada a objetos introduce el encapsulamiento como herramienta para ocultar las partes privadas de un objeto de los consumidores de su parte pública. Es decir, el encapsulamiento nos sirve para proteger la implementación interna de las funcionalidades del objeto.
Los ámbitos de variables
En JavaScript conviene saber una cosa antes de enseñar el encapsulamiento y es nada menos que saber como funcionan los ámbitos de variables.
En C# tenemos los siguientes ámbitos de variables
- de bloque (dentro de un if, o dentro de un for)
- de método (dentro de un método)
- de clase (variables disponibles a todo el código dentro de una clase)
En JavaScript tenemos sólo un ámbito de variable
- de función
En JavaScript, la variable "sube" hasta la función más cercana y allí se queda. No estará disponible para el código JavaScript fuera de esa función pero sí al código que haya dentro de la función. Para que la cosa sea aún peor, la variable hace el "hoisting" y puede machacar las variables con el mismo nombre, como se recoge en el siguiente ejemplo:
var miVariable = 42;
function **foo**() {
alert("Espero 42: " + miVariable);
miVariable = 123;
if (true) {
var **miVariable** = 21;
}
}
foo();
Aquí podemos ver que la variable miVariable dentro del bloque if "sube" hasta la función foo() y se queda como "interna". Sin embargo, en el primer alert el valor de la variable será "undefined". La variable externa miVariable se quedará con el valor 42.
Es importante entender el comportamiento de este trozo de código. Si necesitáis refrescar conceptos, repasad el artículo anterior de la serie de posts.
Variables "globales" y el objeto Window
¿Qué pasa con la variable externa miVariable? Esta variable no tiene ninguna función a la que subir, ¿verdad? Pues resulta que en JavaScript toda variable no asignada dentro de una función sube hasta el objeto window que está disponible en todo el runtime de JavaScript.
Podemos decir que declarar una variable "global" y una variable dentro de window es equivalente. Podéis comprobar este comportamiento en este enlace.
Cierres (Closures)
En muchos lenguajes de programación existe el concepto de cierre (closure). Un cierre es básicamente el mantenimiento del valor de una variable después de que el proceso en el que se ha usado esa variable haya acabado. Es como si el valor de esa variable esté "cerrado" dentro del proceso.
Para explicar este concepto, nada mejor que un ejemplo.
var FactoriaSaludos = **function (nombre) {
** var miPrefijo = 'Hola, ';
return **function() {**
return miPrefijo + nombre;
**}**;
**}**
var saludador = FactoriaSaludos('Edin');
alert(saludador());
En este ejemplo tenemos una función FactoriaSaludos que toma un parámetro nombre. Dentro de ella, devolvemos otro objeto función. Lo que pasa es que esa función anónima interna usa variables que están fuera de su ámbito (miPrefijo y nombre). En este caso, el objeto devuelto (saludador) de tipo función tendrá "cerradas" dentro de él esas dos variables.
Por eso, al invocar la función saludador, nos devuelve un saludo con los valores "cerrados" en el closure de la función interna.
Si este primer ejemplo no te ha parecido muy complejo, prueba con el segundo.
for (var i = 0; i < 10; i++) {
document.getElementById('box' + i).onclick = function() {
alert('You clicked on box #' + i);
};
}
En este ejemplo, al clicar en un div con el nombre box1 esperamos obtener el mensaje de "You clicked on box #1". Sin embargo, podéis verificar que todos los divs devuelven el mismo valor: 5.
¿Qué ha pasado? Pues que la función anónima de onclick ha capturado la variable i. Al clicar, se invoca el valor de la i (es decir, el cierre tiene la variable capturada por referencia) que será siempre el último en actualizarse (es decir 5). Para prevenir esto tenemos que introducir una función interna más, que asegurará que el valor capturado será el del parámetro de entrada de la función externa, evitando la referencia directa a la variable i.
for (var i = 0; i < 10; i++) {
document.getElementById('box' + i).onclick = **function(index)** **{**
return **function()** **{**
window.alert('You clicked on box #' + index);
**}**
**}(i);**
}
La función asignada al onclick toma como parámetro index el valor de la variable i y la función interna lo recoge como cierre . Si ahora clicamos en cada caja, muestra su valor correcto.
¿Todavía no tienes claro como funcionan los cierres? No te preocupes, los cierres parecen muy raros hasta que se prueban en el código. Juega con JSFiddle y prueba los ejemplos que hay en internet hasta que lo entiendas bien.
En la próxima entrega veremos como podemos jugar con cierres para simular encapsulamiento público y privado en JavaScript.