Hay un sinfín de artículos relacionados a las diferencias entre let
, const
y var
. Sin embargo cuando se habla de var normalmente la conclusión es dejar de utilizarlo. La verdad no vengo a desmentir eso, ni a sugerir una forma nueva de hacer las cosas. Pero lo interesante no es si se abandona o no sino el saber el porqué, ¿Por qué es eso considerado una buena práctica?
Bueno la razón mas importante es por el lexical scope. (alcance léxico)
Verás, lexical scope y var tenían una buena vida juntos. Pero un día…algo cambio, lexical scope busco nuevos horizontes mientras que var se quedó en su zona de comfort.
Lo que debes saber
Cuando se habla de var siempre se tiene que hablar del pasado. En las épocas de antaño antes de la llegada de Ecmascript 6, var era la única manera de declarar variables. Aunque bueno de seguro dirás -Si ya sabía eso, es de lo o primero que te dicen cuando te presentan a let
y a const
– lo cual…es cierto. Pero lo que no siempre te dicen es que en ese entonces el lexical scope se dividía solo en 2: global scope y local scope.
-¡Dios mío! ¿Que diantres es eso de los scopes?-
Bueno, si no sabes eso pues no deberías estar leyendo esto. Ve rapidito aquí a aprender acerca del lexical scope y entonces ya vuelves
…
¿Ya sabes qué es? La respuesta debe ser sí y por lo tanto ya sabes la información de las siguiente tablitas.
Scopes | EcmaScript 5 y anteriores | EcmaScript 6 en adelante |
block scope | No | Si |
local scope | Si | Si |
global scope | Si | Si |
Keywords para declarar variables | EcmaScript 5 y anteriores | EcmaScript 6 en adelante |
let | No | Si |
const | No | Si |
var | Si | Si |
Con esta información en mente entenderás que el problema es que anteriormente no existía el block scope y por ende var
jamás fue hecho para tomar en cuenta esa funcionalidad.
Veamos un par de ejemplos con var
y let
/const
let
y const
Puedes ver a let
y const
como los sucesores de var. Al ser los mas recientes es ideal aprender como funcionan primero.
Claro, a diferencia de var
ambos se comportan adecuadamente con cualquiera de los 3 scopes.
Ejemplo 1
En el siguiente código verás declarada una variable global y otra local:
*Nota: Para los ejemplos que verás utilizaré solamente let
, sin embargo podría haber utilizado const
y se comportaría de la misma manera. Cuando hablamos de scope tienen el mismo comportamiento.
let outputPath = '../../resources/output'
function createNiceDocument() {
let niceOptions = {
margin: 'perfect',
textColor: 'darkGray',
}
console.log('outputPath: localScope = ', outputPath)
console.log('niceOptions: localScope = ', niceOptions)
//TODO: createDocument
}
createNiceDocument()
console.log('outputPath: globalScope = ', outputPath)
console.log('niceOptions: globalScope = ', niceOptions)
Al ejecutarlo, este programa regresa el siguiente resultado:
outputPath: localScope = ../../resources/output
niceOptions: localScope = { margin: 'perfect', textColor: 'darkGray' }
outputPath: globalScope = ../../resources/output
...\src\var_let_const__lexicalScope\tempCodeRunnerFile.js:15
console.log('niceOptions: globalScope = ', niceOptions)
^
ReferenceError: niceOptions is not defined
Como uno esperaria, se respeta perfectamente el global y el local scope.
niceOptions
– la variable local- Solo se puede alcanzar dentro del local scope de la función createNiceDocument
- Lanza un error si se intenta llamar en el global scope
outputPath
– la variable global- Se llama correctamente sin importar si la mandas llamar desde el global scope o el local scope.
Ejemplo 2
Ahora un ejemplo agregando un bloque if:
let outputPath = '../../resources/output'
function createNiceDocument() {
let niceOptions = {
margin: 'perfect',
textColor: 'darkGray',
}
//No nos compliquemos, hagamos que el código siempre entre a este if
if(true) {
let pagesNumber = 3
console.log('niceOptions: blockScope? = ', niceOptions)
console.log('pagesNumber: blockScope? = ', pagesNumber)
}
console.log('niceOptions: localScope = ', niceOptions)
console.log('pagesNumber: localScope = ', pagesNumber)
//TODO: createDocument
}
createNiceDocument()
Funciona también como uno esperaría:
pagesNumber
– la variable de bloque- Solo se puede alcanzar dentro del block scope del if que esta dentro de la función
createNiceDocument
- Lanza un error si se intenta llamar ya sea en el local scope de la función o en el global scope
- Solo se puede alcanzar dentro del block scope del if que esta dentro de la función
niceOptions
– la variable local- Se llama correctamente sin importar si la mandas llamar desde el local scope o el block scope.
niceOptions: blockScope? = { margin: 'perfect', textColor: 'darkGray' }
pagesNumber: blockScope? = 3
niceOptions: localScope = { margin: 'perfect', textColor: 'darkGray' }
...\src\var_let_const__lexicalScope\tempCodeRunnerFile.js:17
console.log('pagesNumber: localScope = ', pagesNumber)
^
ReferenceError: pagesNumber is not defined
var
Ahora es el turno de var
. ¿Cómo se comportará el código de los ejemplos anteriores si reemplazamos let
con var
?
Ejemplo 1
Primero el escenario evaluando el scope global y el local:
var outputPath = '../../resources/output'
function createNiceDocument() {
var niceOptions = {
margin: 'perfect',
textColor: 'darkGray',
}
console.log('niceOptions: localScope = ', niceOptions)
//TODO: createDocument
}
createNiceDocument()
console.log('outputPath: globalScope = ', outputPath)
console.log('niceOptions: globalScope = ', niceOptions)
outputPath: localScope = ../../resources/output
niceOptions: localScope = { margin: 'perfect', textColor: 'darkGray' }
outputPath: globalScope = ../../resources/output
..\src\var_let_const__lexicalScope\tempCodeRunnerFile.js:15
console.log('niceOptions: globalScope = ', niceOptions)
^
ReferenceError: niceOptions is not defined
Si te fijas no hay diferencia alguna con el ejemplo de let
y const
. Para estos casos podrías utilizar cualquiera de las 3 keywords indistintamente.
Ejemplo 2
Ahora, ¿qué pasa en el escenario con el bloque if?
var outputPath = '../../resources/output'
function createNiceDocument() {
var niceOptions = {
margin: 'perfect',
textColor: 'darkGray',
}
//No nos compliquemos, hagamos que el código siempre entre a este if
if(true) {
var pagesNumber = 3
console.log('niceOptions: blockScope? = ', niceOptions)
console.log('pagesNumber: blockScope? = ', pagesNumber)
}
console.log('niceOptions: localScope = ', niceOptions)
console.log('pagesNumber: localScope = ', pagesNumber)
//TODO: createDocument
}
createNiceDocument()
Si nos quedamos con la idea de que se debe tener un block scope, pagesNumber
debería ser solo accesible dentro de nuestro if. Por lo que esté código debería arrojar un error:
niceOptions: blockScope? = { margin: 'perfect', textColor: 'darkGray' }
pagesNumber: blockScope? = 3
niceOptions: localScope = { margin: 'perfect', textColor: 'darkGray' }
pagesNumber: localScope = 3
…Pero ese no es el caso. Esto se debe precisamente a lo que te comentaba: var no toma al block scope en cuenta y lo trata como si fuera un local scope. La forma en la que logra esto, es a través de un proceso llamado hoisting. (Este también es un tema importante así que velo anotando para aprenderlo después)
Esta clase de comportamiento es la razón porla que se recomienda utillzar let
/const
en lugar de var
.
No obstante aún hay desarrolladores que le ven la utilidad a var
. Más que nada como una forma visual de señalar que una variable es global.
Personalmente me parece una buena idea. Es una forma visual y sencilla de identificar cierto comportamiento en el código. Pero tampoco soy radical en cuanto a ello. Tu podrás formarte tus propias conclusiones al respecto.
Por mi parte yo soy feliz mientras yo no vea código con var dentro de una función o peor aún…dentro de un bloque if, while, for
o lo que sea.
BONUS
Yo creo que ya deberías de saber la diferencia entre let
y const
. Estar leyendo el tema de Lexical Scope sin saber eso es como abrir un libro y saltarse al capítulo 10 en vez de comenzar con el 1.
Pero en el raro caso de que no sepas o se te haya olvidado, te lo digo rapido.
let
- Se puede modificar el valor despues de haber sido inicializado
const
- No se puede modificar el valor despues de inicializarse