Un tema complejo en el mundo de la programación es el de los ámbitos, scopes o contextos. Cuando comenzamos a aprender sobre variables y constantes, es bueno aprender y entender bien los ámbitos, ya que es la zona de alcance que tiene una variable.
En principio, existen dos ámbitos muy bien definidos:
- Ámbito global: Existe a lo largo de todo el programa o aplicación.
- Ámbito local: Existe sólo en una pequeña región del programa.
Normalmente, esto se determina muy fácilmente observando las llaves {
y }
(curly braces), que abren un nuevo ámbito o contexto.
Ámbitos de variables
Cuando inicializamos una variable al principio de nuestro programa y le asignamos un valor, ese valor generalmente está disponible a lo largo de todo el programa. Sin embargo, esto puede variar dependiendo de múltiples factores. Se conoce como ámbito de una variable a la zona donde esa variable existe.
Por ejemplo, si consultamos el valor de una variable antes de inicializarla, no existe:
console.log(e); // Uncaught ReferenceError: e is not defined
let e = 40;
console.log(e); // 40 (existe porque ya se ha inicializado en la línea anterior)
En el ejemplo anterior, el ámbito de la variable e
comienza a partir de su inicialización y "vive" hasta el final del programa. A esto se le llama ámbito global y es el caso más sencillo. Observa que en la primera línea la variable e
no existe y da un error, pero tras la linea del let
, si devuelve su valor porque ahí si existe.
Sin embargo, esto se puede ir complicando a medida que vamos abriendo llaves de instrucciones como if
, else
, for
o while
. Observa el siguiente ejemplo:
let a = 1;
console.log(e); // Uncaught ReferenceError: e is not defined
if (a == 1) {
let e = 40;
console.log(e); // 40, existe
}
console.log(e); // Uncaught ReferenceError: e is not defined
Observa que la variable a
existe en un ámbito global, mientras que la variable e
sólo existe en el interior del if
: es un ámbito local.
Es puede volverse un poco más complicado dependiendo de si utilizar let
, const
o var
, o dependiendo de la anidación. Lo iremos viendo a medida que avancemos.
Ámbitos de variables: let y const
En las versiones modernas de Javascript (a partir de ECMAScript 2015), se introduce la palabra clave let
en sustitución de la antigua var
para declarar variables y const
para declarar constantes. Tanto con let
como con const
, estaremos utilizando los ámbitos clásicos de programación: ámbito global y ámbito local.
Veamos otro ejemplo, esta vez utilizando un bucle for
:
console.log("Antes: ", p); // En este punto, p no está definida
for (let p = 0; p < 3; p++) {
console.log("Valor de p: ", p); // Aquí, p estará definida como 0, 1, 2
}
console.log("Después: ", p); // En este punto, vuelve a no estar definida
Varios detalles:
- Utilizando
let
en el buclefor
, la variablep
sólo está definida dentro del bucle (ámbito local) - Observa que tanto antes como después del bucle,
p
no existe.
Aunque omitamos las llaves del
for
(en este caso es posible porque sólo contiene una línea), los ámbitos siguen existiendo. Hacemos referencia a las llaves porque se ve mejor, pero lo que importa es si se declara la variable dentro del buclefor
o no.
Ámbitos de variables: var (legacy)
Aunque declarar variables con var
ya se considera legacy (obsoleto) y no debe usarse, si te interesa saber un poco más por si te lo encuentras, en este enfoque tradicional de Javascript, existen ámbitos diferentes: el ámbito global y el ámbito a nivel de función.
La diferencia se puede ver claramente en el uso de un bucle for
con var
(a diferencia del que vimos anteriormente con let
):
console.log("Antes: ", p); // En este punto, p vale undefined
for (var p = 0; p < 3; p++) {
console.log("Valor de p: ", p); // Aquí, p estará definida como 0, 1, 2
}
console.log("Después: ", p); // Después: 3 (WTF!)
Esto es algo que a un programador acostumbrado a otros lenguajes con ámbitos tradicionales le puede explotar la cabeza. Si utilizamos var
la variable p
sigue existiendo fuera del bucle, aunque haya sido declarada en su interior. Esto ocurre porque en ese caso se usa un ámbito a nivel de función.
Veamos este otro ejemplo (necesitaras conocer el concepto de función, por lo que si no lo conoces, quizás necesites volver más tarde cuando hayas visto ese tema):
var a = 1;
console.log(a); // Aquí accedemos a la "a" global, que vale 1
function x() {
console.log(a); // En esta línea el valor de "a" es undefined
var a = 5; // Aquí creamos una variable "a" a nivel de función
console.log(a); // Aquí el valor de "a" es 5 (a nivel de función)
console.log(window.a); // Aquí el valor de "a" es 1 (ámbito global)
}
x(); // Aquí se ejecuta el código de la función x()
console.log(a); // En esta línea el valor de "a" es 1
En el ejemplo anterior vemos que el valor de a
dentro de una función no es el 1
inicial, sino que estamos en otro ámbito diferente donde la variable a
anterior no existe: un ámbito a nivel de función. Mientras estemos dentro de una función, las variables inicializadas en ella estarán en el ámbito de la propia función.
Estamos usando el objeto especial
window
para acceder directamente al ámbito global independientemente de donde nos encontremos. Esto ocurre así porque las variables globales se almacenan dentro del objetowindow
(la pestaña actual del navegador web). Hoy en día, en lugar de utilizarwindow
sería preferible utilizarglobalThis
.
Sin embargo, si eliminamos el var
de la línea var a = 5
del ejemplo anterior, observa la diferencia:
var a = 1;
console.log(a); // Aquí accedemos a la "a" global, que vale 1
function x() {
console.log(a); // En esta línea el valor de "a" es 1
a = 5; // Aquí creamos una variable "a" en el ámbito anterior
console.log(a); // Aquí el valor de "a" es 5 (a nivel de función)
console.log(window.a); // Aquí el valor de "a" es 5 (ámbito global)
}
x(); // Aquí se ejecuta el código de la función x()
console.log(a); // En esta línea el valor de "a" es 5
En este ejemplo se omite el var
dentro de la función, y vemos que en lugar de crear una variable en el ámbito de la función, se modifica el valor de la variable a
a nivel global. Dependiendo de donde y como accedamos a la variable a
, obtendremos un valor u otro.
Siempre que sea posible se debería utilizar
let
yconst
, en lugar devar
. Declarar variables mediantevar
se recomienda en fases de aprendizaje o en el caso de que se quiera mantener compatibilidad con navegadores muy antiguos utilizando ECMAScript 5, sin embargo, hay estrategias mejores a seguir que utilizarvar
en la actualidad.