La antigua «var»
Este artículo es útil para entender código antiguo. La información aquí es relevante para interpretar y trabajar con código más viejo.
No es así como escribimos código moderno.
En el primer capítulo sobre variables, mencionamos tres formas de declarar una variable:
let
const
var
La declaración var
es similar a let
. Casi siempre podemos reemplazar let
por var
o viceversa y esperar que el código funcione:
var mensaje = "Hola";
alert(mensaje); // Hola
Sin embargo, internamente var
es diferente, originario de tiempos muy antiguos. Generalmente no se usa en código moderno, pero aún aparece en código viejo.
Si no planeas encontrarte con tal código, puedes saltar este capítulo o posponerlo, pero hay posibilidades de que esta diferencia pueda afectarte más tarde.
Por otro lado, es importante entender las diferencias cuando se migra código antiguo de var
a let
para evitar errores extraños.
var
no tiene alcance de bloque
Las variables declaradas con var
pueden tener a la función como entorno de visibilidad o ser globales. Su visibilidad atraviesa los bloques.
Por ejemplo:
if (true) {
var test = true; // uso de "var" en lugar de "let"
}
alert(test); // true, la variable vive después del if
Como var
ignora los bloques de código, tenemos una variable global test
.
Si usáramos let test
en vez de var test
, la variable sería visible solamente dentro del if
:
if (true) {
let test = true; // uso de "let"
}
alert(test); // ReferenceError: test no está definido
Lo mismo sucede en los bucles: var
no puede ser local en los bloques ni en los bucles:
for (var i = 0; i < 10; i++) {
var uno = 1;
// ...
}
alert(i); // 10, "i" es visible después del bucle, es una variable global
alert(uno); // 1, "uno" es visible después del bucle, es una variable global
Si un bloque de código está dentro de una función, var
se vuelve una variable a nivel de función:
function decirHola() {
if (true) {
var frase = "Hola";
}
alert(frase); // funciona
}
decirHola();
alert(frase); // ReferenceError: frase no está definida
Como podemos ver, var
atraviesa if
, for
u otros bloques. Esto es porque mucho tiempo atrás los bloques en JavaScript no tenían entornos léxicos, y var
es un remanente de aquello.
var
tolera redeclaraciones
Declarar la misma variable con let
dos veces en el mismo entorno es un error:
let usuario;
let usuario; // SyntaxError: 'usuario' ya fue declarado
Con var
podemos redeclarar una variable muchas veces. Si usamos var
con una variable ya declarada, simplemente se ignora:
var usuario = "Pedro";
var usuario = "Juan"; // este "var" no hace nada (ya estaba declarado)
// ...no dispara ningún error
alert(usuario); // Juan
Las variables var
pueden ser declaradas debajo del lugar donde se usan
Las declaraciones var
son procesadas cuando se inicia la función (o se inicia el script para las globales).
En otras palabras, las variables var
son definidas desde el inicio de la función, sin importar dónde esté tal definición (asumiendo que la definición no está en una función anidada).
Entonces el código:
function decirHola() {
frase = "Hola";
alert(frase);
var frase;
}
decirHola();
…es técnicamente lo mismo que esto (se movió var frase
hacia arriba):
function decirHola() {
frase = "Hola"; // (*)
if (false) {
var frase;
}
alert(frase);
}
decirHola();
Este comportamiento también se llama “hoisting” (elevación), porque todos los var
son “elevados” hacia el tope de la función.
Entonces, en el ejemplo anterior, la rama if (false)
nunca se ejecuta, pero eso no tiene importancia. El var
dentro es procesado al iniciar la función, entonces al momento de (*)
la variable existe.
Las declaraciones son “hoisted” (elevadas), pero las asignaciones no lo son
Es mejor demostrarlo con un ejemplo:
function decirHola() {
alert(frase);
var frase = "Hola";
}
decirHola();
La línea var frase = "Hola"
tiene dentro dos acciones:
- La declaración
var
- La asignación
=
.
La declaración es procesada al inicio de la ejecución de la función (“elevada”), pero la asignación siempre se hace en el lugar donde aparece. Entonces lo que en esencia hace el código es:
function decirHola() {
var frase; // la declaración se hace al inicio...
alert(frase); // undefined
frase = "Hola"; // ...asignación - cuando la ejecución la alcanza.
}
decirHola();
Como todas las declaraciones var
son procesadas al inicio de la función, podemos referenciarlas en cualquier lugar. Pero las variables serán indefinidas hasta que alcancen su asignación.
En ambos ejemplos de arriba, alert
se ejecuta sin un error, porque la variable frase
existe. Pero su valor no fue asignado aún, entonces muestra undefined
.
IIFE
Como en el pasado solo existía var
, y no había visibilidad a nivel de bloque, los programadores inventaron una manera de emularla. Lo que hicieron fue usar «expresiones de función inmediatamente invocadas» (abreviado IIFE en inglés).
No es algo que debiéramos usar estos días, pero puedes encontrarlas en código antiguo.
Un IIFE se ve así:
(function() {
var mensaje = "Hola";
alert(mensaje); // Hola
})();
Aquí la expresión de función es creada e inmediatamente llamada. Entonces el código se ejecuta enseguida y con sus propias variables privadas.
La expresión de función es encerrada entre paréntesis (function {...})
, porque cuando JavaScript se encuentra con «function» en el flujo de código principal, lo entiende como el principio de una declaración de función. Pero una declaración de función debe tener un nombre, entonces ese código daría error:
// Trata de declarar e inmediatamente llamar una función
function() { // <-- SyntaxError: la instrucción de función requiere un nombre de función
var mensaje = "Hola";
alert(mensaje); // Hola
}();
Incluso si decimos: “okay, agreguemos un nombre”, no funcionaría, porque JavaScript no permite que las declaraciones de función sean llamadas inmediatamente:
// error de sintaxis por causa de los paréntesis debajo
function ir() {
}(); // <-- no puede llamarse una declaración de función inmediatamente
Entonces, los paréntesis alrededor de la función son un truco para mostrarle a JavaScript que la función es creada en el contexto de otra expresión, y de allí lo de “expresión de función”, que no necesita un nombre y puede ser llamada inmediatamente.
Existen otras maneras además de los paréntesis para decirle a JavaScript que queremos una expresión de función:
// Formas de crear IIFE
(function() {
alert("Paréntesis alrededor de la función");
})();
(function() {
alert("Paréntesis alrededor de todo");
}());
!function() {
alert("Operador 'Bitwise NOT' como comienzo de la expresión");
}();
+function() {
alert("'más unario' como comienzo de la expresión");
}();
En todos los casos de arriba, declaramos una expresión de función y la ejecutamos inmediatamente. Tomemos nota de nuevo: Ahora no hay motivo para escribir semejante código.
Resumen
Hay dos diferencias principales entre var
y let/const
:
- Las variables
var
no tienen alcance de bloque: su visibilidad alcanza a la función, o es global si es declarada fuera de las funciones. - Las declaraciones
var
son procesadas al inicio de la función (o del script para las globales).
Estas diferencias casi siempre hacen a var
peor que let
. Las variables a nivel de bloque son mejores. Es por ello que let
fue introducido en el estándar hace mucho tiempo, y es ahora la forma principal (junto con const
) de declarar una variable.