Contextos y encapsulamiento en Javascript
Tras unos meses sin decir ni pío, aquí dejo un artículo contestando a una petición de un tal jose, que espero que sea de interés.
Aunque este aspecto es de la programación en general, el encapsulamiento es una parte de Javascript que puede traer a muchos por la calle de la amargura. Aunque no es un tema especialmente complejo requiere unos minutos de reflexión para asegurarse de que se domina. Si no acabaremos con un programa que suelta variables por todas partes. El primer paso para comprender el encapsulamiento es la pila y el contexto de ejecución.
Durante la ejecución de un programa se van ejecutando varias funciones cada una con sus variables y sus operaciones. Cada una de estas llamadas guarda un contexto que no es más que un pequeño espacio de memoria con todas las variables a las que la función tiene acceso (entre otras cosas que no vienen al caso). Este contexto permanece en memoria hasta que la función termina su ejecución y posteriormente desaparece para siempre. Este contexto creado se guarda en una estructura de memoria llamada pila. Y, ¿por qué se llama pila? Pues porque funciona exactamente como una pila de elementos. Atentos a la explicación con el siguiente código:
var ga = 'ga'; function fa () { var va = 'va'; console.log(va); } function main () { var vb = 'vb'; fa(); console.log(vb); } main();La pila de ejecución del programa anterior es la siguiente:
En este ejemplo he omitido la memoria que necesitan la propia declaración de las funciones por sencillez, pero en el futuro hay que tener en cuenta que las funciones cuentan.
- Al principio del programa se crea una variable ga que se guarda en un contexto global, es decir, ese contexto que todo el mundo dice que no toquéis o recibiréis carbón por navidad (prometo que al final de esta entrada entenderéis por qué si no lo sabéis).
- El contexto global se coloca en la pila con toda la memoria que necesite y la ejecución continúa.
- Al llega a la llamada a la función main se crea un nuevo contexto para dicha función y al llegar a la declaración de vb se añade dicha variable.
- Dentro de main tenemos una llamada a una función llamada fa que necesita un nuevo contexto. Además se añaden las variables de esta función al contexto y se continúa.
- Cuando la función fa termina se elimina su contexto y el contexto de main queda en el tope de la pila para continuar la ejecución.
- Al terminar la función main se libera el siguiente contexto y posteriormente se libera el contexto global para terminar el programa por completo. ¡Hemos terminado!
Este es el funcionamiento básico de la pila de ejecución de un programa y es necesario conocerlo para la siguiente parte: el encapsulamiento. La razón es que tiene un proceso bastante similar excepto porque el encapsulamiento se calcula en la declaración de todas las funciones y no en su ejecución. Esto no quiere decir que realmente los navegadores lo hagan de esta forma, sino que es la forma más sencilla de entender el resultado final.
La parte complicada viene cuando en Javascript se pueden declarar funciones donde nos de la gana incluyendo el interior de otras funciones. Esto hace que posean un encapsulamiento algo más complejo ya que según dónde las hayamos declarado tendrán acceso a unas variables u otras. Una vez entendido ésto y que tenemos que fijarnos en dónde están declaradas y no en dónde se ejecutan es fácil predecir qué variables serán accesibles. A continuación hay algunos ejemplos:
function fa () { var va = 'va'; function fb () { var vb = 'vb'; // fa, va, fb, vb } // fa, va, fb } // fa
En este primer caso declaramos una función fa y otra función fb dentro de la primera. Tal y como se espera dentro de fb podemos acceder a las variables declaradas dentro de fa porque ha sido declarada en su interior:
function fc () { var vc = 'vc'; // fc, vc } function fa () { var va = 'va'; function fb () { var vb = 'vb'; fc(); // fa, va, fb, vb, fc } // fa, va, fb, fc } // fa, fc
Ahora tenemos dos funciones fa y fc que pertenecen al contexto global y pueden ser invocadas desde cualquier parte. Aquí puede verse uno de los casos expuestos anteriormente: aunque fc se ejecuta dentro de fb no tiene acceso a va ni a vb porque fue declarada fuera de este encapsulamiento.
var fc; function fa () { var va = 'va'; function fb () { var vb = 'vb'; fc = function () { var vc = 'vc'; // fa, va, fb, vb, fc, vc }; // fa, va, fb, vb, fc } // fa, va, fb, fc } // fa, fc
En este último ejemplo declaramos fc como variable global pero definimos la función en el interior de fb, como consecuencia dentro de fc tenemos acceso al encapsulamiento de todas las funciones que envuelven a la declaración y a sus variables. Estos son los conceptos esenciales del encapsulamiento y comprenderlos es importante para dominar la programación en Javascript. Espero que os sean útiles.
Como hemos visto antes, la declaración de variables globales es algo tabú cuando estamos programando y el motivo es tan simple como que una variable global es... sorpresa, sorpresa: global. Es accesible desde cualquier parte del programa y como consecuencia es fácil que nos de un dolor de cabeza si en alguna parte de nuestro código la modificamos sin saber dónde.