Las palabras clave let
y const
son dos nuevos tipos de métodos para declarar variables en JavaScript. Una palabra clave let
es similar a una palabra clave var
en muchos aspectos, pero let
permite a los desarrolladores evitar algunos de los problemas que suelen ocurrir en JavaScript. La declaración const
es una forma de evitar que se reseteen valores a ciertas variables.
Dado que TypeScript es un superconjunto de JavaScript, el lenguaje admite let
y const
. Explicaremos cómo se declaran estas variables y por qué es mejor usarlas en lugar de var
.
Si tienes poca experiencia con JavaScript, el siguiente párrafo te ayudará a evaluar tus conocimientos. Si está familiarizado con el funcionamiento de las declaraciones var
en JavaScript, puede ignorar el siguiente párrafo.
Tabla de contenidos
- Declaraciones de var
- Declaraciones let
- Re-declaraciones y Shadowing
- Declaraciones const
- Diferencia entre declaraciones let y const
- Desestructuración
- Operador spread
- Recursos del Artículo
Declaraciones de var
Las variables en JavaScript siempre se han declarado con la palabra clave var
.
var a = 10;
En el código anterior, declaramos una variable con el nombre a
y le asignamos el valor 10
. También podemos declarar una variable dentro de una función de la siguiente manera:
function f() { var message = "¡Hola, mundo!"; return message; }
También podemos acceder a estas variables dentro de otras funciones de la siguiente manera:
function f() { var a = 10; return function g() { var b = a + 1; return b; } } var g = f(); g(); // devuelve '11'
En el ejemplo anterior, la función g capturó la variable a
definida en la función f
. Cuando se llama a la función g, un valor a
se asociará con un valor a
dentro de la función f
. Incluso si se llama g
a la función en el momento en que la función f
deja de ejecutarse, aún podrá acceder a la variable a y cambiarla.
function f() { var a = 1; a = 2; var b = g(); a = 3; return b; function g() { return a; } } f(); // devuelve '2'
Reglas de alcance
Reglas de dominio en declaraciones var
son extrañas para alguien que está acostumbrado a trabajar en otros lenguajes de programación, por ejemplo:
function f(shouldInitialize: boolean) { if (shouldInitialize) { var x = 10; } return x; } f(true); // devuelve '10' f(false); // devuelve 'indefinido'
Algunas personas pueden sorprenderse con este ejemplo, ya que la variable se declaró dentro x
de un bloque , pero aun así logramos acceder a ella desde fuera del bloque. Esto se debe a que se puede acceder a las declaraciones desde cualquier lugar dentro de una función, módulo, espacio de nombres o ámbito global que contenga el código, independientemente del bloque que contenga el código. Esto a menudo se denomina alcance de var o alcance de función. Y los parámetros también están en el dominio de la función. ifvar
Estas reglas de dominio pueden causar una serie de errores. Permitir que la misma variable sea redeclarada más de una vez es uno de los problemas que estas reglas exacerban:
function sumMatrix(matrix: number[][]) { var sum = 0; for (var i = 0; i < matrix.length; i++) { var currentRow = matrix[i]; for (var i = 0; i < currentRow.length; i++) { sum += currentRow[i]; } } return sum; }
Tenga en cuenta que el ciclo for
interno i
sobrescribirá involuntariamente el valor de la variable, porque la variable i
apunta a la misma variable dentro del alcance de la función, lo que alterará la forma en que funciona el programa. Dichos errores pueden pasar por alto las revisiones de código, lo que dificulta la corrección de errores.
Peculiaridades de captura de variables
Intenta adivinar la salida del siguiente código:
for (var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); }
La función setTimeout intenta ejecutar una función después de un número específico de milisegundos (pero espera a que terminen de ejecutarse otras operaciones). Veamos cual es la salida:
10 10 10 10 10 10 10 10 10 10
Si la salida te sorprende, no estás solo, la mayoría de la gente esperará lo siguiente:
0 1 2 3 4 5 6 7 8 9
Recuerde lo que dijimos antes sobre la captura de variables, cada expresión de función setTimeout
que pasemos apuntará a la misma variable i
dentro del mismo campo.
Esto significa que la función setTimeout
se ejecutará después de una milésima de segundo, pero solo después de que el ciclo for haya terminado. Cuando un bucle for
se detiene, el valor de la variable i
es 10
. ¡Entonces el valor se imprimirá 10
cada vez que se llame a la función!
Las expresiones IIFE (expresión de función inmediatamente invocada) se utilizan a menudo para resolver este problema capturando la variable i
en cada iteración:
for (var i = 0; i < 10; i++) { // captura el estado actual de 'i' // invocando una función con su valor actual // captura el estado de una variable // confiando en un función cuyo valor se pasa actual (function(i) { setTimeout(function() { console.log(i); }, 100 * i); })(i); }
Este código de aspecto extraño es común en las aplicaciones de JavaScript. El parámetro i en la lista de parámetros anula la variable declarada en el ciclo for
, pero no cambiamos mucho el contenido del ciclo porque los nombramos a ambos.
Declaraciones let
La instrucción let
se agregó para resolver los problemas causados por var
. Y la forma de escribir oraciones let
es similar a la forma de escribir oraciones var
:
let hello = "Hello!";
La verdadera diferencia está en la forma en que funciona y la semántica, no en la sintaxis.
Alcance del bloque
Cuando una variable se declara usando let
, significa que está usando lo que se llama alcance léxico o alcance de bloque. A diferencia de las variables declaradas con una palabra clave var
cuyos campos se filtran en la función contenedora, no se puede acceder a las variables declaradas en un campo de bloque fuera del bloque o bucle contenedor más cercano for
.
function f(input: boolean) { let a = 100; if (input) { // referencia a la variable // 'a' let b = a + 1; return b; } // Falso, la variable // 'b' // no se encuentra aquí, porque la definimos en el bloque de condiciones return b; }
Aquí tenemos dos variables locales, la variable a y la variable b
. Un dominio a
finito en el cuerpo de la función f
y b
un dominio finito en el bloque if
contenedor. Las variables declaradas en un bloque catch
también tienen bases de dominio similares:
try { throw "oh no!"; } catch (e) { console.log("Oh well."); } // Error, variable 'e'no encontrada aquí console.log(e);
Las variables no se pueden leer (obtener su valor) o escribir (asignar un valor) antes de que se declaren, lo cual es una de las ventajas de las variables finitas en los campos de bloque. Si bien estas variables están presentes en su totalidad, todos los lugares que anteceden a donde se declaran forman parte de su zona muerta temporal. Esta declaración simplemente significa que no puede acceder a las variables antes de la declaración _ let
que declara y afortunadamente, TypeScript le dirá eso para evitar errores.
a++; // la variable no se puede usar antes de declarar let a;
Nota: Aún puede capturar una variable de campo de bloque antes de declararla. Sin embargo, no se puede llamar a la función contenedora antes de que se declare la variable. Si está basado en ES2015, el tiempo de ejecución generará una excepción. Sin embargo, TypeScript actualmente permite esto y no le dirá que es un error.
function foo() { // captura de variables 'a' aquí se permite return a; } // No se puede llamar a la función antes de que se declare la variable // Aquí se lanzará un error de tiempo de ejecución foo(); let a;
Para obtener más información sobre el rango de muerte temporal, consulte esta págin de Mozilla.
Re-declaraciones y Shadowing
Cuando se usan declaraciones var
, la variable se puede volver a declarar un número infinito de veces; pero terminarás con una sola variable:
function f(x) { var x; var x; if (true) { var x; } }
En el código anterior, todas las declaraciones sobre x
se refieren a la misma variable x
, lo cual es correcto. Pero a veces acaba siendo fuente de males. Pero las declaraciones que let
resuelven estos problemas no son de la misma manera:
let x = 10; let x = 20; //Error, no se puede volver a declarar la variable en el mismo campo
No es necesario que ambas variables estén limitadas por rango en el bloque para que TypeScript nos diga que hay un problema.
function f(x) { let x = 100; // Falso, esto contradice la declaración del parámetro de la función } function g() { let x = 100; var x = 100; // Falso, la variable no se puede declarar de ninguna manera }
Esto no significa que las variables con ámbito de bloque no se puedan declarar con una variable con ámbito de función. La variable de dominio de bloque solo necesita declararse dentro de un bloque claramente diferente.
function f(condition, x) { if (condition) { let x = 100; return x; } return x; } f(false, 0); // devuelve '0' f(true, 0); // devuelve '100'
Insertar un nuevo nombre en un campo interno llamado sombreado. Es un arma de doble filo en cierto modo, porque puede traer otros males en caso de que se cubra sin querer, al mismo tiempo que protege contra otros males también. Por ejemplo, imagina si escribimos la función sumMatrix
anterior usando let
.
function sumMatrix(matrix: number[][]) { let sum = 0; for (let i = 0; i < matrix.length; i++) { var currentRow = matrix[i]; for (let i = 0; i < currentRow.length; i++) { sum += currentRow[i]; } } return sum; }
Esta versión del bucle de iteración realizará la suma correctamente porque la variable i
dentro del bucle y anula la variable i
en el bucle exterior. Por lo general , es mejor evitar la cobertura para escribir código limpio y puro. Aunque hay algunos casos en los que el uso de la cobertura es adecuado, debes tomar tu decisión con cautela.
Captura de variables con ámbito de bloque
Cuando se nos ocurrió la idea de capturar variables al usar declaraciones var
, observamos cómo se comportan las variables cuando se capturan. Para que la idea quede más clara, tenga en cuenta que cada vez que se ejecuta un determinado dominio, se creará un ‘entorno’ de variables. Este entorno y las variables capturadas en él pueden estar disponibles incluso después de que todo en su alcance haya terminado de ejecutarse.
function theCityThatAlwaysSleeps() { let getCity; if (true) { let city = "Seattle"; getCity = function() { return city; } } return getCity(); }
Debido a que hemos capturado city
dentro de su entorno, aún podemos acceder a ella aunque la ejecución del bloque _ if
haya expirado. Recuerde el ejemplo setTimeout
anterior, en el que necesitábamos la expresión IIFE para capturar el estado de la variable para cada iteración del for
. Estábamos creando un nuevo entorno de variables para nuestras variables capturadas. Y eso fue un poco complicado, pero afortunadamente, nunca necesitarás hacer esto en TypeScript.
Las declaraciones tienen un mecanismo let
cuando se usan dentro de bucles. En lugar de crear un nuevo entorno en el ciclo, las declaraciones crean un nuevo campo para cada iteración. Y dado que ese es el punto de la expresión IIFE que usamos, podemos cambiar nuestro ejemplo setTimeout
anterior para usar solo declaraciones let
.
for (let i = 0; i < 10 ; i++) { setTimeout(function() { console.log(i); }, 100 * i); }
Y el resultado esta vez será el esperado:
0 1 2 3 4 5 6 7 8 9
Declaraciones const
Las sentencias const
son otra forma de declarar variables.
const numLivesForCat = 9;
Son similares a las declaraciones let
, pero como indica el nombre de la declaración (una constante), los valores de las variables declaradas son inmutables después de que se vinculan. En otras palabras, las declaraciones const
siguen las mismas reglas de dominio que las declaraciones const
, pero no puede restablecerlas.
Esto no quiere decir que los valores indicados por las variables sean inmutables:
const numLivesForCat = 9; const kitty = { name: "Aurora", numLives: numLivesForCat, } // Error, el valor no se puede cambiar a otro valor kitty = { name: "Danielle", numLives: numLivesForCat, } // Está bien cambiar el contenido del valor al que apunta la variable kitty.name = "Rory"; kitty.name = "Kitty"; kitty.name = "Cat"; kitty.numLives--;
Tenga en cuenta que aún puede cambiar el estado interno de las variables const
, a menos que tome medidas especiales para evitarlo. Afortunadamente, TypeScript le permite especificar elementos de un objeto determinado para que sean de solo lectura ( readonly
).
Diferencia entre declaraciones let y const
Debido a que ambos métodos son similares en términos de cómo funciona el campo, es natural preguntarse qué método es mejor y cuándo usar qué tipo de afirmación. La respuesta es que depende del propósito de usar la variable.
Aplicando el principio de privilegio mínimo, todas las declaraciones que no planee cambiar deben usar const
. Si no es necesario asignar un nuevo valor a una variable, alguien que trabaje en el código no debería poder hacerlo automáticamente, y alguien que quiera cambiar el valor de una variable lo pensará dos veces antes de hacerlo. Usar const
esto también facilita la lectura y explicación del código y el análisis del flujo de datos.
Piénsalo dos veces si estás trabajando en equipo, consulta a tus compañeros. Esta documentación utiliza let
declaraciones mayor parte del tiempo.
Desestructuración
La descompilación es una característica nueva de ECMAScript 2015 disponible en TypeScript. Para obtener una referencia completa sobre el desmontaje, consulte esta página de Mozilla.
desestructuración de matriz
Descomponer matrices y asignar sus elementos a varias variables es una de las formas más simples de descomposición:
let input = [1, 2]; let [first, second] = input; console.log(first); // 1 console.log(second); // 2
Esto crea dos nuevas variables llamadas first
, y second
. Este es el equivalente a usar la indexación, pero es más directo y simple que la indexación:
first = input[0]; second = input[1];
El desmontaje también funciona con variables previamente declaradas:
// intercambiar variables, cada una con el valor de la otra [first, second] = [second, first];
También se puede utilizar con los operandos de funciones:
function f([first, second]: [number, number]) { console.log(first); console.log(second); } f([1, 2]);
Puede crear una variable que contenga el resto de los elementos de la lista usando la ...
siguiente :
let [first, ...rest] = [1, 2, 3, 4]; console.log(first); // 1 console.log(rest); // [ 2, 3, 4 ]
Y dado que estamos trabajando con JavaScript, también puede ignorar los elementos no deseados restantes:
let [first] = [1, 2, 3, 4]; console.log(first); // 1
o ignorar otros elementos:
let [, second, , fourth] = [1, 2, 3, 4];
Desestructuración de objetos
También podremos descomponer objetos:
let o = { a: "foo", b: 12, c: "bar" }; let { a, b } = o;
Esto crea dos nuevas variables (a
, b
) de o.a
y o.b
. Tenga en cuenta que puede omitir c
si no lo necesita. Y al igual que la descomposición de matrices, también puede asignar sin declarar variables:
({ a, b } = { a: "baz", b: 101 });
Tenga en cuenta que tuvimos que encerrar esta oración entre paréntesis. Esto se debe a que JavaScript considera el símbolo { como el comienzo de un bloque. Puede crear una variable que contenga el resto del objeto usando la estructura ...
de la siguiente manera:
let { a, ...passthrough } = o; let total = passthrough.b + passthrough.c.length;
Cambio de nombre de propiedad
También puede nombrar las propiedades:
let { a: newName1, b: newName2 } = o;
La sintaxis general aquí es un poco complicada, puedes leer el pasaje como a: newName1
si estuvieras diciendo: Toma el valor a
del objeto y ponlo en la variable newName1
. La dirección es de izquierda a derecha, como si escribieras lo siguiente:
let newName1 = o.a; let newName2 = o.b;
Lo que lo hace confuso aquí es que los dos puntos (:) no expresan tipo. Si se especifica el tipo, debe especificarse después del desmontaje completo de la siguiente manera:
let { a, b }: { a: string, b: number } = o;
Valores predeterminados
Los valores predeterminados le permiten especificar un valor predeterminado para una propiedad si no está definida:
function keepWholeObject(wholeObject: { a: string, b?: number }) { let { a, b = 1001 } = wholeObject; }
En este ejemplo, la función ahora keepWholeObject
tiene una variable wholeObject
además de las propiedades a
y b
, incluso si la propiedad b
no está definida. Su valor por defecto será el número 1001
.
Declaraciones de funciones
La descomposición también funciona cuando se declaran funciones. Esto es evidente en casos simples:
type C = { a: string, b?: number } function f({ a, b }: C): void { // ... }
Determinar valores predeterminados para los parámetros es muy común y combinarlo con el desmontaje puede ser difícil. Primero, debe recordar especificar el patrón antes del valor predeterminado:
function f({ a, b } = { a: "", b: 0 }): void { // ... } f(); // el valor predeterminado es // { a: "", b: 0 }
Nota: La sección anterior es un ejemplo de una inferencia de tipos.
Luego, deberá recordar establecer los valores predeterminados para los opcionales en la propiedad suelta en lugar de la parte principal, recordando que with C
se define b
como opcional:
function f({ a, b = 0 } = { a: "" }): void { // ... } // el valor predeterminado es b = 0 f({ a: "yes" }); // el primer valor predeterminado es // { a: "" } // lo que lleva al valor predeterminado b = 0 f(); // Falso porque 'a' es // una propiedad requerida cuando pasamos un valor a f({});
Utilice el desmontaje con precaución. Como muestra el ejemplo anterior, las expresiones de deconstrucción se pueden transformar fácilmente en código complejo. Y la descomposición anidada aumenta esta complejidad, lo que puede hacer que la comprensión del código sea difícil y laboriosa incluso sin usar el cambio de nombre, los valores predeterminados y las anotaciones de tipo. Trate de mantener las expresiones de descomposición pequeñas y simples. Siempre puede escribir las asignaciones generadas por la descompilación usted mismo, lo que hará que el código sea más fácil de leer y comprender.
Operador spread
El operador spread es lo contrario de desenredar. Permite esparcir un arreglo a otro arreglo, o un objeto a otro objeto por ejemplo:
let first = [1, 2]; let second = [3, 4]; let bothPlus = [0, ...first, ...second, 5];
Esto le da el valor [0, 1, 2, 3, 4, 5]
a la matriz bothPlus
. La publicación copia superficialmente tanto first
como second
, por lo que la publicación no los altera. También puede publicar objetos:
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" }; let search = { ...defaults, food: "rich" };
Esto hará que el objeto search se vea así :
{ food: "rich", price: "$$", ambiance: "noisy" }
Publicar objetos es más complejo que implementar arreglos. Al igual que con el despliegue de matrices, el proceso se ejecuta de izquierda a derecha, pero el resultado sigue siendo un objeto. Esto significa que las propiedades que están al final del objeto de publicación se sobrescriben y sobrescriben las propiedades que están al principio del mismo. Entonces, si modificamos el ejemplo anterior y publicamos al final del objeto:
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" }; let search = { food: "rich", ...defaults };
La propiedad food
en el objeto escribirá defaults
en la propiedad food: "rich"
, lo que es un resultado no deseado en este caso, así que tenga cuidado al publicar objetos. Para difundir los objetos algunos obstáculos sorprendentes también. En primer lugar, una publicación solo incluye las propiedades que posee y que son contables (consulte esta página de Mozilla ). Lo que simplemente significa que perderá métodos cuando publique instancias del objeto:
class C { p = 12; m() { } } let c = new C(); let clone = { ...c }; clone.p; // ok clone.m(); //Error, el método no se une a la nueva versión creada por la publicación
En segundo lugar, el compilador de TypeScript no le permite publicar parámetros de tipo de funciones genéricas. Sin embargo, se espera que esta función esté disponible en futuras versiones del lenguaje.
Recursos del Artículo
- Múltiples Constantes en TypeScript
- Iteradores y generadores en TypeScript
- Símbolo en TypeScript (Symbol)
- Tipos Avanzados en TypeScript
- Tipos de Compatibilidad en TypeScript
- Inferir Tipos en TypeScript
- Tipos Generalizados (Generics) en TypeScript
- Tipos Básicos de Datos en TypeScript
- Interfaces en TypeScript
- Declaración de Variables en TypeScript
- Funciones en TypeScript
- Categorías en TypeScript
- Introducción a TypeScript