2 ejemplos para entender la diferencia entre var y let

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.

ScopesEcmaScript 5 y anterioresEcmaScript 6 en adelante
block scopeNoSi
local scopeSiSi
global scopeSiSi
Keywords para declarar variablesEcmaScript 5 y anterioresEcmaScript 6 en adelante
letNoSi
constNoSi
varSiSi

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
  • 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