La información en este artículo es útil para entender código antiguo.
No es así como escribimos código moderno.
En el primer capítulo acerca de 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 las cosas funcionen:
var message = "Hola";
alert(message); // Hola
Pero internamente var
es una bestia diferente, originaria de muy viejas épocas. Generalmente no se usa en código moderno, pero aún habita en el antiguo.
Si no planeas encontrarte con tal código bien puedes saltar este capítulo o posponerlo, pero hay posibilidades de que esta bestia pueda morderte más tarde.
Por otro lado, es importante entender las diferencias cuando se migra antiguo código de var
a let
para evitar extraños errores.
“var” no tiene alcance (visibilidad) de bloque.
Las variables declaradas con var
pueden: tener a la función como entorno de visibilidad, o bien 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 para los bucles: var
no puede ser local en los bloques ni en los bucles:
for (var i = 0; i < 10; i++) {
var one = 1;
// ...
}
alert(i); // 10, "i" es visible después del bucle, es una variable global
alert(one); // 1, "one" 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 sayHi() {
if (true) {
var phrase = "Hello";
}
alert(phrase); // funciona
}
sayHi();
alert(phrase); // ReferenceError: phrase 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 ambientes 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 user;
let user; // SyntaxError: 'user' ya fue declarado
Con var
podemos redeclarar una variable muchas veces. Si usamos var
con una variable ya declarada, simplemente se ignora:
var user = "Pete";
var user = "John"; // este "var" no hace nada (ya estaba declarado)
// ...no dispara ningún error
alert(user); // John
Las variables “var” pueden ser declaradas debajo del lugar en 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, no importa dónde esté tal definición (asumiendo que la definición no está en una función anidada).
Entonces el código:
function sayHi() {
phrase = "Hello";
alert(phrase);
var phrase;
}
sayHi();
…es técnicamente lo mismo que esto (se movió var phrase
hacia arriba):
function sayHi() {
var phrase;
phrase = "Hello";
alert(phrase);
}
sayHi();
…O incluso esto (recuerda, los códigos de bloque son ignorados):
function sayHi() {
phrase = "Hello"; // (*)
if (false) {
var phrase;
}
alert(phrase);
}
sayHi();
Este comportamiento también se llama “hoisting” (elevamiento), porque todos los var
son “hoisted” (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 sayHi() {
alert(phrase);
var phrase = "Hello";
}
sayHi();
La línea var phrase = "Hello"
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 (“hoisted”), pero la asignación siempre se hace en el lugar donde aparece. Entonces lo que en esencia hace el código es:
function sayHi() {
var phrase; // la declaración se hace en el inicio...
alert(phrase); // undefined
phrase = "Hello"; // ...asignación - cuando la ejecución la alcanza.
}
sayHi();
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 phrase
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 el llamado "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 message = "Hello";
alert(message); // Hello
})();
Aquí la expresión de función es creada e inmediatamente llamada. Entonces el código se ejecuta enseguida y con sus variables privadas propias.
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 message = "Hello";
alert(message); // Hello
}();
Incluso si decimos: “okay, agreguémosle 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 go() {
}(); // <-- no puede llamarse una declaración de función inmediatamente
Entonces, los paréntesis alrededor de la función es 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) .
Hay otra diferencia menor relacionada al objeto global que cubriremos en el siguiente capítulo.
Estas diferencias casi siempre hacen a var
peor que let
. Las variables a nivel de bloque son mejores. Es por ello que let
fue presentado en el estándar mucho tiempo atrás, y es ahora la forma principal (junto con const
) de declarar una variable.
Comentarios
<code>
, para varias líneas – envolverlas en la etiqueta<pre>
, para más de 10 líneas – utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen…)