HowTo donde se explica el uso correcto de las palabras clave var y la introducción de let y const en ECMAScript 6>. Aprenderemos cuando se produce hoisting, qué es la Zona temporal muerta (TDZ) y el patrón de diseño IIFE.
Variables (bindings)
Entendemos como variable (también conocida binding) a un contenedor, el cual podrá guardar cualquier tipo de valor.
En este tutorial aprenderás a usar correctamente las palabras clave let y const (de ECMAScript 6>) y var, con las que podrás declarar variables y también aprenderás cómo afecta cada una de ellas a su alcance.
Antes de explicar el comportamiento de cada una de ellas por separado será será mejor entender cómo hemos llegamos a esto.
La palabra clave «var» y el hoisting
Desde su creación en 1995, JavaScript siempre ha tenido una forma un poco extraña de tratar la declaración de variables.
Podíamos hacer uso de la «palabra clave» var para definir el nombre de una variable para así poder asignarle un valor, teniendo esta un alcance local en la que ha sido declarada.
Las declaraciones de variables usando la palabra clave var son izadas al inicio del contexto donde son asignadas y procesadas en el ámbito al que pertenecen (a esto lo llamamos hoisting).
Para entender este término lo mejor es verlo en funcionamiento con un ejemplo y ver cómo la variable value cambia de valor al recibir esta un nuevo valor.
1 2 3 4 5 6 7 8 9 10 |
function setValue(condicion) { if (condicion == true) { var value = 3; // value = 3 console.log(value); } else { console.log(value); // value = undefined } console.log(value); // value = 3 or value = undefined } setValue(false); |
Por lo que en muchas ocasiones los programadores desconocían el comportamiento de una declaración hoisting y sin entender el porqué se producía un bug.
Esta es una de las razones de porqué ECMAScript 6 introdujo las nuevas palabras clave (let y const), para poder tener un control del alcance de las variables a nivel de bloque.
Declaraciones a nivel de Bloque
Todas las declaraciones de variables a nivel de bloque son inaccesibles fuera del alcance de este.
Entendemos por declaraciones de bloque a todas aquellas que están dentro de una función o dentro de un bloque delimitado por llaves o paréntesis.
- function { … }
- { … } ó ( … )
La palabra clave let
La sintaxis de una declaración usando la palabra clave let es la misma que var. La diferencia es el la limitación del alcance de esta al bloque en el que ha sido declarada.
Las declaraciones usando let no serán izadas al inicio del bloque (hoisted), por lo que es recomendable ubicarlas al inicio de este si queremos que estén disponibles en todo el bloque.
Veamos un ejemplo del comportamiento a nivel de bloque de una declaración usando let.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function setValue(condicion) { // value no existe en el bloque if (condicion == true) { let value = 3; console.log(value); // value = 3 } else { // value no existe en el bloque console.log(value); // Uncaught ReferenceError: value is not defined } // value no existe en el bloque console.log(value); // Uncaught ReferenceError: value is not defined } setValue(true); |
En caso de no ubicar la declaración al inicio del bloque se produciría una zona temporal muerta (Temporal Dead Zone, TDZ) donde la variable no existirá y esto lanzará un error.
1 2 3 4 5 6 7 8 |
function setValue(condicion) { if (condicion == true) { // temporal dead zone (TDZ) console.log(value); // Uncaught ReferenceError: value is not defined let value = 3; // value = 3 } } setValue(true); |
no a la redeclaración
Si ya existe una declaración de variable con el mismo nombre y estando esta dentro del mismo bloque, no se permitirá la redeclaración por lo que se lanzará un error de sintáxis.
1 2 3 4 5 6 7 8 9 |
function setValue(condition) { if (condition == true) { var value = 10; console.log(value); // value = 10 let value = 20; console.log(value); // Uncaught SyntaxError: Identifier 'value' has already been declared } } setValue(true); |
La palabra clave const
Otra forma de definir declarar variables en ECMAScript 6> es usando la palabra clave const. Todas las variables declaradas usando const serán consideradas como constantes por lo que “sus valores no pueden ser modificados” una vez establecidos.
Veamos como no se nos permite asignar un nuevo valor a la constante.
1 2 3 |
const value = 10; console.log(value) // value = 10 value = 11; // Uncaught TypeError: Assignment to constant variable. |
Por lo que siempre debemos asignar un valor a la constante al ser declarado.
1 |
const value; // Uncaught SyntaxError: Missing initializer in const declaration |
const vs let
Tanto const como let son declaraciones a nivel de bloque por lo que no serán accesibles fuera del bloque en el que fueron declaradas. Las declaraciones nunca serán izadas (hoisted).
⇒ Tanto let como const no permiten la redeclaración de variables.
1 2 3 4 |
var value = 1; const value = 10; // Uncaught SyntaxError: Identifier 'value' has already been declared var value = 1; let value = 10; // Uncaught SyntaxError: Identifier 'value' has already been declared |
La diferencia entre let y const es que let permite reasignar un nuevo valor a la variable y const al ser una constante no permite cambiar su valor inicialmente asignado.
1 2 3 |
let value = 1; value = 10; console.log(value); // value = 10 |
1 2 3 |
const value = 1; value = 10; console.log(value); // const.html:38 Uncaught TypeError: Assignment to constant variable. |
Cuando usamos const con objetos, se nos permite cambiar el valor de sus propiedades.
1 2 3 4 5 6 |
const evento = { nombre: "reunión", fecha: "3 de abril 2022" }; evento.nombre = "charla"; console.log(evento.nombre); // evento.nombre = charla |
Declaraciones a nivel de bloque en bucles
Una de los usos más comunes de variables es en bucles donde una variable que hace de contador es usando en el bucle.
El el ejemplo vemos como la variable i aún es accesible fuera del bloque (for) ya que la variable declarada usando var es izada (hoisted).
1 2 3 4 |
for (var i=0; i<5; i++) { console.log(i); } console.log(i); // i también es accesible fuera del bloque |
El mismo ejemplo usando let la variable i ya no será accesible fuera del bloque. Se nos indica que la variable i no ha sido definida.
1 2 3 4 |
for (let i=0; i<5; i++) { console.log(i); // i es accesible } console.log(i); // Uncaught ReferenceError: i is not defined |
Cuando usamos const se produce un error ya que inicialmente a i se la asigna un valor y posteriormente se le intenta reasignar otro nuevo valor (que no está permitido cuando usamos constantes).
1 2 3 4 |
for (const i = 0; i<5; i++) { console.log(i); // i es accesible inicialmente } console.log(i); // Uncaught TypeError: Assignment to constant variable. |
Funciones en bucles
El uso de var dentro de funciones que están dentro de bucles siempre han creado muchos problemas, ya que las variables también son accesibles fuera del bucle.
Veamos el problema con un ejemplo.
1 2 3 4 5 6 7 8 9 10 11 12 |
var funcs = []; // creamos un array for (var i = 0; i < 10; i++) { funcs.push(function() { // incluimos la función con el valor de i console.log(i); }); } console.dir(funcs); // veamos como es el contenido del array // recorremos el array funcs.forEach(function(func) { func(); // ejecutar la funcion del array// imprime 10, 10 veces }); |
Para evitar este problema se crearon las IIFE (Immediately Invoked Function Expression) que es una función JavaScript que se ejecuta cuando esta es definida.
IIFE es un patrón de diseño que contiene 2 partes:
- Por un parte una función anónima que impide que evita el acceso a sus variables fuera de su alcance.
- Y la segunda parte es la que crea una expresión para poder ejecutar dicha la función de forma inmediata usando ().
1 2 3 4 5 6 7 8 9 10 11 |
var funcs = []; // creamos un array for (var i = 0; i < 10; i++) { funcs.push((function(value) { // incluimos la función con el valor de i return function() { console.log(value); } }(i))); // autoejecutamos la función } funcs.forEach(function(func) { func(); // imprime del 0 al 9 }); |
Afortunadamente hacer uso de let en el primer ejemplo simplifica este problema, puesto que let crea una nueva variable i con cada iteración.
Comportamientos en ámbito global (global scope)
Entendemos como global scope cuando una podemos definir una misma variable en diferentes partes del programa.
Cuando var es usada globalmente se crea una nueva variable global que será propiedad del objeto window (accesible desde el browser object model, BOM).
El problema es que accidentalmente podemos sobrescribir el valor de dicha variable.
1 2 3 4 |
var mensaje = "Hola"; console.log(window.mensaje); // Hola var mensaje = "Hola!"; console.log(window.mensaje); // Hola! |
Tanto let como const se comportan de manera diferente en este caso ya que no crean una nueva variable en el objeto window.
1 2 |
let mensaje = "Hola"; console.log(window.mensaje); // undefined |
Por lo que en ciertos casos podríamos necesitar definir variables usando var para que acceder a ellas desde el objeto window.
Conclusiones
- Tanto let como const nos evitan que redeclararemos una variable o constante accidentalmente por lo que no podrán compartir su nombre con una función u otra variable en el mismo bloque en el que han sido declaradas.
- Solo las variables declaradas con let permiten la reasignación de valores.
Por lo que si queremos que nuestros códigos sean más limpios y legibles usaremos const y let en los siguientes casos:
let para cuando necesitemos que una variable sea reasignada.
- const cuando necesitemos declarar una constante y no queramos que esta sea reasignada (salvo con objetos)
- Usaremos var cuando necesitemos definir variables y cuyo valor necesitemos accederlo desde el objeto window.
Bibliografía y Webgrafía
- Zakas, N. Understanding ECMAScript 6.
- Tutorial, T. (2019). The Modern JavaScript Tutorial. Retrieved from https://javascript.info/
- IIFE: Expresión de función ejecutada inmediatamente. (2019). Retrieved from https://developer.mozilla.org/es/docs/Glossary/IIFE