feb 14 2013

LET – variables locales al ámbito de bloque

Let es una de las muchas mejoras previstas para la versión 6 de EcmaScript que la mayoría de navegadores/máquinas virtuales JS no soportan.

No es una aportación sustancial al lenguaje. Principalmente está descrita para alejar a los nuevos programadores de las malas prácticas que el lenguaje ofrece a los noobs.

SpiderMonkey (JS de Firefox) implementa en gran medida esta funcionalidad. A partir de la versión 1.7 de JavaScript podemos usar estas y otras mejoras, pero siempre es necesario indicar que versión de javascript se debe ejecutar.

Esta instrucción nos permite jugar con variables locales. Esto no es novedad en javascript, lo novedoso es que su ámbito local es el bloque de código, no la función.

En mis pruebas he comprobado que Firefox acepta el type del script como texto o aplicación. Este es el type recomendado, pero ambos funcionan.

<script type="text/javascript;version=1.7">...</script>
<script type="application/javascript;version=1.7">...</script>

-Let como Declaración-

En el primer console.log baz no se le ha asignado valor, pero dentro de ese ámbito ha sido reservada ese identificador.

<script type="text/javascript;version=1.7">
function yeah(){
    var foo="aaa";
    {
        console.log(foo, baz);// aaa, undefined
        let baz="zzz";
        console.log(foo, baz);// aaa, zzz
    }
    console.log(foo, baz);// ReferenceError: baz is not defined
}

yeah();
</script>

En el ámbito de la función no existe la variable baz puesto que es local al bloque. Aunque podemos acceder a las variables de este ámbito de función y de bloque, estos no son el mismo. Con la sentencia Let restringimos el acceso de ámbito al bloque. Con la sentencia var solo se restringe a la función.

Redeclaraciones

No están permitidas, así de simple. Este es un punto de rigidez mayor que con Var donde están completamente permitidas.

En el siguiente ejemplo mientras que con foo no tenemos problemas, con let no están permitidas múltiples declaraciones. Sin mostrar por consola ninguna línea anterior se nos devuelve un error de tipos:

TypeError: redeclaration of variable baz

<script type="text/javascript;version=1.7">
function yeah(){
    var foo="aaa";
    var foo="bbb";
    {
        var foo="ccc";
        let baz="zzz";
        console.log(foo, baz);
        let baz="_";//TypeError: redeclaration of variable baz
    }
    console.log(foo, baz);
}

yeah();
</script>

Sucede de la misma manera si declaramos baz en un bloque interior pero con la declaración var, con lo que las dos variables estarían en conflicto.

<script type="text/javascript;version=1.7">
function yeah(){
    var foo="aaa";
    var foo="bbb";
    {
        var foo="ccc";
        let baz="zzz";
        console.log(foo, baz);
        {
            var baz="_";//TypeError: redeclaration of variable baz
        }
    }
    console.log(foo, baz);
}

yeah();
</script>

Sacando el segundo baz a otro bloque ya no encontramos ningún problema en la nueva declaración. Estamos en otro bloque con distinto ámbito de bloque.

<script type="text/javascript;version=1.7">
function yeah(){
    var foo="aaa";
    var foo="bbb";
    {
        var foo="ccc";
        let baz="zzz";
        console.log(foo, baz);// ccc, zzz
    }
    console.log(foo, baz);// ccc, undefined
    let baz="_";
    console.log(foo, baz);// ccc, _
}

yeah();
</script>

Mezclando variables globales, locales y locales al bloque

Veamos el comportamiento a través de bloques y funciones. Modificamos una variable global en la línea 2 de la función:

<script type="text/javascript;version=1.7">
var a="aaa";
function yeah(){
    console.log(a);// aaa
    a="bbb";
    {
        console.log(a);// undefined
        let a="zzz";
        console.log(a);// zzz
    }
    console.log(a);// bbb
}

yeah();
console.log(a);// bbb
</script>

Dentro del bloque la variable ‘a’ global existe, pero también existe otra variable local a la función llamada ‘a’, solo que aun no se le ha asignado ningún valor.

Una vez asignado el string “zzz” se puede mostrar, pero al ser local al bloque no se puede trabajar con ella fuera del bloque.

Es importante tener en cuenta que un bloque viene definido solamente por llaves ( { } ). Aunque ciertas expresiones como if y for puedan tener efecto para sentencia siguiente sin bloque (se puede entender que está dentro de un bloque indirectamente) el caso de Let es estricto en el bloque definido por llaves.

<script type="text/javascript;version=1.7">
if (false) var x = 9; // legal
if (false) let x = 9; // SyntaxError: let declaration not directly within block
</script>

-Let como Expresión-

Una expresión let limita el alcance de la variable declarada a sólo esa expresión.

De esta manera podemos usar let para usar una variable en una sentencia solamente.

var a = 5;
let(a = 6) alert(a); // 6
alert(a); // 5

Claro que como las funciones son instrucciones, podemos utilizar una para ejecutar todo el código que queramos con determinado valor local

<script type="text/javascript;version=1.7">
var a = 5;
let(a = 6) (function(){
    console.log(a);// 6
    a+=100;
    console.log(a);// 106
})();
console.log(a); // 5
</script>

Pero todos los valores declarados con let seguirán siendo locales a esa expresión, sin existir fuera de ella.

<script type="text/javascript;version=1.7">
var a = 5;
let( a = 6, b=2000 )(function(){
    console.log(a);// 6
    a+=100 + b;
    console.log(a);// 2106
})();
console.log(a); // 5
console.log(b); // ReferenceError: b is not defined
</script>

El uso de Let como expresión de esta última manera lleva de forma natural a pensar en usarla como una sentencia más.

-Let como Sentencia-

La sintaxis es muy parecida a la expresión, y tiene precedentes en otras sentencias como for, while, if, … así que en definitiva podemos decir que es una expresión más que genera un ámbito local.

<script type="text/javascript;version=1.7">
var global_a = 10;
var global_b =20
let( a = global_a, b=2000 ){
    console.log(a);// 10
    a+=100 + b;
    console.log(a);// 2110
    global_b = a/2;
};
console.log(global_a); // 10
console.log(global_b); // 1055
console.log(a); // ReferenceError: a is not defined
</script>

así con Let creamos un ámbito en el que existen variables privadas y locales para ese ámbito.

-Let con otras sentencias-

Ya que tenemos una intrucción que declara variables locales al bloque, porqué no combinarla con las que ya existen.

Si declaramos variables con Let en un for en la parte de inicialización obtendremos variables locales al for.

<script type="text/javascript;version=1.7">
for(let i=0; i<5; i++){
    //...
    console.log(i);// 0 1 2 3 4
}
console.log(i); // ReferenceError: i is not defined
</script>

Así i desaparece al acabar el for, que es cuando acaba el bloque de la sentencia.

Lo mismo sucede en el caso de for..in y for..of para el uso de Let.

En conclusion:

Muchas veces la forma de programar en JS es como es porque sólo define ámbito una función. Usando let podemos jugar con los ámbitos de bloque como en otros lenguajes clásicos. Pero esto no le quita a var sus propiedades tradicionales de definicion de variables.

-Problemas con Let-

Redeclaraciones de parámetros

La redeclaración de un parámetro de la función con Let y con var es un tema pendiente. Las redeclaraciónes con var sobreescriben el valor del parámetro, si, pero las redeclaraciones con var están permitidas en cualquier bloque y función tantas veces como se desee. Con let no es igual.

La redeclaracion de un parámetro con var y su posterior uso modifica el objeto arguments en la posición donde aparece la variable.

function fun(a, b, c){

    var b = 1000;
    return arguments;

}
console.log(fun("a1", "b2", "c3")); // ["a1", 1000, "c3"]

Existe una discusión en la comunidad de si debería ser igual con Let en este caso.

function f(a) {

    let a = 42;
    return arguments[0]

}
console.log(f(7));// PROBLEMA -> 42 ¿debería ser 7 o 42?

Los chicos de Mozilla dicen que sí, por eso en su implementación la redeclaración de parámetros funciona igual en let que en var.

Let en primer nivel

Este si que es un bug real de mozilla y es que Let en primer nivel (nivel de función) se comporta como Var. Permite redeclaraciones, cosa que no debería ser posible. En mozilla tienen documentado el bug (https://bugzilla.mozilla.org/show_bug.cgi?id=589199) y está en sus planes de para completar la implementación de ecmascript 6