Ámbitos y Alcance en Javascript

[Fuente: http://pensamientoobjetivo.blogspot.com.es/2009/09/ambitos-y-alcance-en-javascript.html]

Dependiendo de los lenguajes programación en los que hemos programado con anterioridad, en Javascript nos podemos topar con algunas sorpresas, y no todas ellas son agradables, Específicamente, puede ser un problema si venimos de un background de lenguajes con alcance de bloque (c, c++, java, etc.) o sin él (basic, pascal, etc.). Ya que el alcance en Javascript puede ser la fuente de bugs muy difíciles de encontrar, a continuación hacemos un pequeño recuento de cómo funcionan los ámbitos de variable en Javascript.

El ámbito “global”

En la mayor parte de los navegadores web actuales, el objeto window es el espacio global que contiene todas las funciones base del lenguaje y todas las variables y funciones que serán definidas en este.

Si se declara una variable, usted verá que usted puede hayarla en el objeto window:

var miAncho = 100;
miAltura = 200;

alert( window['miAncho'] ); // "100"

alert( window.miAncho );    // también muestra "100"

alert( window['miAltura'] ); // "200"
alert( window.miAltura === miAltura ); // "true"

Obviamente, no todo es accesible desde el objeto window, ya que en Javascript podemos usar el ámbito de nivel de función.

Declaración de variables

Tal vez esto sea obvio para muchos, pero para mi inicialmente fue una duda que durante algún tiempo limitó mi entendimiento de Javascript. En Javascript, es perfectamente válido declarar una variable con la palabra reservada var ¡O sin ella! (a todos los que hemos programado en Basic alguna vez, nos trae hermosos recuerdos de lo grandioso que nos parecía esta “característica”… hasta que nos ocasionó nuestro primer dolor de cabeza).

La idea básica que debemos entender es que var no es solo lo que la abreviación parece indicar: una declaración de una variable, sino por el contrario, es la definición del alcance de la misma. En el ejemplo anterior hemos usado ambas notaciones y podemos ver que en el caso del alcance global, el ámbito de la variable no se modifica.

// Al estar en el ámbito global, ambas lineas
// son equivalentes
var miAltura = 100;
miAltura = 100;

En el espacio de nombres global, la instrucción var no tiene ninguna repercusión, por lo que podemos hacer algo como esto:

var miAltura = 100;
miAncho = 100;

function muestraVariables() {
 alert( miAltura + ' y ' + miAncho);
}

Las variables declaradas en el ámbito global serán accesibles desde dentro de todas las funciones, pues todas están también contenidas en el objeto window. Sin embargo, las cosas cambian cuando usamos var en el contexto de funciones. A continuación vemos un ejemplo en el que var modifica el alcance de una variable:

var miAltura = 100;
miAncho = 100;

function muestraVariables() {
 alert( miAltura + ' y ' + miAncho);
 var miColor='#000000';
 miFondo='#FFFF00';
}

function muestraColores() {
 alert( miColor + ' y ' + miFondo );
}

muestraVariables(); // "100 y 100"
muestraColores();   // "undefined y #FFFF00"

Aquí es conveniente prestar mucha atención a la manera en que muestraVariables define variables. La variable miColor está definida usando var, mientras que miFondo no la usa. De hecho, cuando una variable es definida sin utilizar var dentro de una función, tras bambalinas esto es como Javascript lo interpretaría:

function muestraVariables() {
 alert( miAltura + ' y ' + miAncho);
 var miColor = '#000000';
 window.miFondo = '#FFFF00';
}

Cada variable que no se defina usando la instrucción var en el contexto de una función, será asignada al objeto window y por lo tanto se convierte en una variable global.

Una variable definida dentro de una función con la palabra var se vuelve inaccesible desde el exterior, y es por ello que podemos decir que efectivamente se convierte en una variable privada.

También es posible crear funciones dentro de otras funciones:

function muestraVariables() {
 var miColor ='#000000';

 var miOtraVariable = function () {
     return miColor;
 }

 alert( miOtraVariable() );  // "#000000"
}

// fuera de la función
alert( miOtraVariable() );   // ¡Error!

Las variables definidas dentro de muestraVariables son inaccesibles fuera de ella, aunque miOtraVariable guarde una referencia a una función. Sin embargo, podemos ver que la función anidada si tiene acceso a miColor, ya que la función misma está definida dentro de muestraVariables.

Por lo anterior, podemos concluir que se debe evitar colocar variables e incluso funciones en el ámbito global, ya que por ejemplo, si se usa el mismo nombre de variable o función en dos archivos JS distintos, el último de ellos “sobreescribe” las definiciones del anterior, lo que en un momento dado implica que ¡Es posible que no estemos trabajando con las funciones o variables que pensamos!

En Javascript, el último en llegar gana.

En realidad, esto no es muy relevante si solo usamos una o dos funciones en una página web, pero cuando tenemos que trabajar en un equipo de desarrollo, cuando utilizamos o tenemos que convivir con código de terceros o cuando incorporamos bibliotecas externas en nuestra aplicación y si todos ellos colocan sus variables y funciones en el contexto global es posible que tengamos un mal rato tratando de entender porqué no obtenemos los resultados que esperamos, ¡Pensando incluso que se trata de un bug en nuestro código!

Alcance de nivel de bloque

En Javascript no existe el ámbito de variables a nivel de bloques, por lo que debemos estar atentos al hecho de que cualquier variable definida dentro de un bloque de código (for, while, if, switch, etc) será accesible desde el ámbito inmediato superior (siguiendo las reglas explicadas anteriormente con respecto al uso de var).

if( miAncho == 100) {
   var miAltura=200;
}
alert( miAltura ); // "200"

Esta es una diferencia fundamental con respecto a Java, en el cual el tiempo de vida de una variable termina al cerrarse el bloque que la contiene.

for(var i=0; i<=100; i++) {
   miColor=i;
}

for(; i<50; i++) {
   miAncho=i;
}

alert(miColor); //100
alert(miAncho); //error

Al definir la variable i con la palabra reservada var no se le da alcance de bloque dentro del ciclo for. Por lo tanto, al entrar al siguiente ciclo, el valor de i es 100, por lo que nunca se entra al segundo ciclo.

Esto no significa que no debamos definir variables dentro de bloques. Es importante que las variables se declaren lo más cerca posible del lugar en el que se usarán para aumentar la claridad de nuestro código. En este aspecto, es similar a utilizar Dim en Basic para declarar una variable dentro de un IF o un WHILE. Aunque posición de la declaración no cambia la semántica de la misma, si tiene un efecto en el potencial de comunicación de esta.

Objetos, Funciones y alcance

Hemos visto que la palabra var solo tiene significado dentro del contexto de una función, lo que nos habilita a tener variables privadas y globales si así lo deseamos. Sin embargo, las funciones nos posibilitan un nivel más de alcance: el “nombre” de la función.

Hemos visto que todas las variables definidas dentro de una función sin la instrucción var son asignadas al objeto window. Ok, pero digamos que queremos escribir código limpio y evitar el uso de variables globales, ¿Cómo podemos hacerlo?

De hecho, fuera de una función, en el ámbito global, podemos usar otro método para referirnos al objeto window mediante otra palabra reservada: this.

La palabra this tiene un papel muy importante en Javascript, pero al mismo tiempo es una fuente de confusión cuando se comienza a programar en este lenguaje.

Poniéndolo de forma llana, this siempre “apunta” al contexto actual en el que se está ejecutando nuestro código. Veamos por ejemplo:

miColor = 100;

alert( this.miColor );    // 100
alert( window.miColor );  // 100
alert( window === this ); // true

Como podemos ver, this en el ámbito global hace referencia al objeto window.

Por otro lado, las funciones también nos permiten utilizar this, pero en este caso, this no se referirá al objeto window. ¿Entonces a qué?

Como se mencionó anteriormente, todas las funciones están siempre asociadas a un objeto. Si la función se declara globalmente, entonces están asociadas al objeto window; si son “métodos” de un objeto, entonces están asociadas a ese objeto. Y this siempre apuntará al contexto dentro del cual se ejecuta una función, por lo que dependiendo de la forma en que llamemos a una función, puede afectar el contexto al que apunta this:

function miFunc() {
  alert( this === window );
}
miFunc();     // true
new miFunc(); // false

En este caso, this está apuntando a algo más que no es el objeto window. Para entender qué es ese algo, veamos cómo interpreta Javascript la última linea del ejemplo:

new miFunc();
 temp = new Object();

 temp.miFunc = miFunc;
 temp.miFunc()

Cuando se usa una función para inicializar un objeto (usando la palabra new, se dice que esta función es el constructor de ese objeto y el nombre de esa función se convierte en la clase del objeto.

Podemos comprobar que this se refiere a un objeto con el nombre de la función utilizando el operador instanceof, que nos dice si un objeto es de una “clase” específica o bien la propiedad constructor:

function miFunc() {
  alert(this instanceof miFunc);
  alert(this.constructor == miFunc);
}

miFunc();     // false, false
new miFunc(); // true, true

Todo esto puede sonar un poco confuso. Trataré de explicarme: Al usar la palabra reservada new se crea un objeto temporal en el background por nosotros, el mismo que se asociará al nombre de nuestra función. Pero, ¿para qué nos sirve esto?

Bueno, en primer lugar nos habilita a utilizar un nivel más de alcance, el de objeto, que es similar al concepto de variables de instancia de los lenguajes orientados a objetos tradicionales. Con esto tenemos 3 niveles distintos:

function pruebaAlcance() {
    global = 100;

    var privada = 200;

    this.publica = 300;

    this.metodoPublico = function() {
         alert( 'puedo ser usado desde el objeto pruebaAlcance' );
    }
    var metodoPrivado = function() {
        alert( '¡No puedes llamarme desde el exterior!' );
    }
}
pruebaAlcance();

alert( global ); // 100

alert( privada ); // error

alert( pruebaAlcance.privada ); // undefined

alert( pruebaAlcance.publica ); // 300

alert( publica ); // undefined

alert( pruebaAlcance.metodoPublico() ) // 'puedo ser... etc'

alert( metodoPublico() ) // error

alert( pruebaAlcance.metodoPrivado() ); // error

alert( metodoPrivado() ); // error

Los niveles de alcance de Javascript pueden parecer extraños en un principio y puede costar trabajo acostumbrarse a ellos en un principio, pero también pueden ser una herramienta poderosa al momento de construir bibliotecas o convivir con código de terceros sin pisar los dedos de nadie más.

Las diferentes formas de declarar variables o métodos dentro de una función u objeto es la base mediante la cual se pueden simular espacios de nombres (namespaces) en Javascript y son la base de las técnicas orientadas a objetos del mismo.

Espero haya sido de su interés.

Este post fue publicado originalmente el 30 de Septiembre de 2008 en un blog anterior y posteriormente perdido en un crash de servidor.