Pregunta 1¿Qué pasa si muestras unit en la línea que sigue a if (true) { const unit = "yen"; }?
Ámbito y hoisting — dónde y desde cuándo se puede leer una variable
El ámbito decide desde dónde se lee una variable, y el hoisting registra las declaraciones antes de ejecutar el código. Incluye shadowing, la TDZ y por qué evitar var.
Si usas fuera de unas llaves un valor que calculaste dentro de ellas, la ejecución se detiene. También se detiene cuando una línea lee un valor de configuración que se declara más abajo. En los dos casos, el problema es desde dónde y desde cuándo se puede usar un nombre.
Este artículo trata el ámbito, que decide desde dónde se puede leer un nombre declarado, y el hoisting, que registra las declaraciones antes de que se ejecute el código.
Variables que solo existen dentro de las llaves — ámbito de bloque
Supón que quieres mostrar en la pantalla del carrito cuánto le falta al cliente para tener envío gratis. Puedes calcular lo que falta dentro del if que comprueba el envío gratis, pero si intentas mostrar ese valor fuera de las llaves, la ejecución se detiene con un ReferenceError.
El ámbito (scope: la zona que decide desde dónde se puede leer un nombre) lo delimitan las llaves. La zona de un par de llaves se llama ámbito de bloque, y una variable declarada con const o let solo se puede leer dentro del ámbito de bloque más interno que rodea su declaración.
const cartTotal = 4800; // Nivel más externo, fuera de cualquier llave
function checkShipping(total) {
const freeLine = 5000; // Solo dentro de las llaves de checkShipping
if (total < freeLine) {
const shortage = freeLine - total; // Solo dentro de las llaves del if
console.log(`Faltan ${shortage} yenes`); // Faltan 200 yenes
}
// Las variables declaradas afuera se pueden leer desde adentro
console.log(cartTotal); // 4800
// console.log(shortage); // ReferenceError: shortage is not defined
}
checkShipping(cartTotal);
const cartTotal = 4800— se puede leer desde cualquier parte del archivocheckShipping(cartTotal)— llama a la función desde el nivel más externo
const freeLine = 5000— solo se puede leer dentro de estas llaves- El
cartTotalde afuera también se puede leer aquí console.log(shortage)— fuera del if ya no se puede leer, así que daReferenceError
const shortage = 200— solo se puede leer dentro de estas llaves- Se pueden leer tanto
freeLinecomocartTotal
if ya no se puede leer al salir de sus llaves. La lectura va en una sola dirección: de adentro hacia afuera.El nivel más externo, fuera de cualquier llave, es el ámbito global, y sus variables se pueden leer desde cualquier parte. El cuerpo de una función también va entre llaves, así que no puedes leer freeLine desde fuera de la función. La zona que delimitan las llaves de una función se llama ámbito de función.
Gana la declaración de adentro — shadowing
Al agregar una función que calcula precios con una tasa de impuesto reducida, puede que declares dentro de ella una variable llamada taxRate, con el mismo nombre que la de afuera. Si no sabes cuál de los dos valores se usa, no vas a poder averiguar por qué un resultado salió mal.
Si en un ámbito interno declaras un nombre que ya existe en uno externo, dentro de esas llaves se usa la declaración de adentro. Esto se llama shadowing (sombreado: una declaración interna oculta una variable externa con el mismo nombre), y pasa tanto con las llaves de un if como con las de una función.
const taxRate = 0.1; // Tasa de impuesto definida afuera
function withTax(price) {
return Math.floor(price * (1 + taxRate));
}
function withReducedTax(price) {
const taxRate = 0.08; // Mismo nombre, declarado dentro de la función
return Math.floor(price * (1 + taxRate));
}
console.log(withTax(1000)); // 1100
console.log(withReducedTax(1000)); // 1080
console.log(taxRate); // 0.1 (la de afuera no cambió)
taxRate encuentra una declaración distinta según dónde lo leas. Una declaración interna no sobrescribe el valor de afuera.Un nombre se busca empezando por las llaves más internas que rodean la línea que lo lee. Dentro de withReducedTax, la búsqueda termina en cuanto encuentra 0.08, así que el 0.1 de afuera nunca se lee. Cuesta seguir un código en el que un mismo nombre vale algo distinto en cada función, así que en tu propio código usa nombres diferentes, como reducedTaxRate.
Sin const, asignas a la variable de afuera
Si dentro de una función escribes taxRate = 0.08; sin const, no se crea ninguna variable nueva: es una asignación al taxRate de afuera. Si la de afuera es const, la ejecución se detiene con un TypeError, pero si es let, el valor se sobrescribe, y hasta una llamada posterior a withTax(1000) devuelve 1080.
No se puede leer antes de la línea de declaración — hoisting y la TDZ
Supón que quieres agrupar valores de configuración, como la tasa de puntos o el costo de envío, en líneas debajo del código que los usa. Como viste en el artículo de funciones, una declaración function se puede llamar aunque esté escrita más abajo, pero si lees un valor de configuración declarado con let o const antes de su línea de declaración, la ejecución se detiene con un ReferenceError.
Antes de empezar a ejecutar un ámbito, JavaScript registra los nombres de las declaraciones que hay en él. Esto se llama hoisting (elevación: las declaraciones se registran antes de que se ejecute el código). Una declaración function registra su nombre y su cuerpo juntos, así que puedes llamarla desde líneas que están arriba de la declaración. let y const registran solo el nombre; el valor se guarda cuando se ejecuta la línea de declaración.
// El cuerpo de la función lee pointRate cuando se la llama
function calcPoint(amount) {
return Math.floor(amount * pointRate);
}
// Si la llamas aquí, la línea de declaración de pointRate todavía no se ejecutó
// console.log(calcPoint(3200));
// → ReferenceError: Cannot access 'pointRate' before initialization
let pointRate = 0.05; // Configuración agrupada más abajo
console.log(calcPoint(3200)); // 160 (se puede leer porque la línea de declaración ya se ejecutó)
let, el valor todavía no existe, aunque lo lea una función escrita más arriba. Que se pueda leer o no depende del orden de ejecución, no de dónde lo escribiste.El tramo que va desde que se entra en un ámbito hasta que se ejecuta la línea de declaración del let se llama TDZ (Temporal Dead Zone, zona muerta temporal: el periodo en el que un nombre no se puede leer hasta que se ejecuta su línea de declaración). Si el mensaje de error dice is not defined, el nombre no está en el ámbito. Si dice before initialization, el nombre existe, pero lo leíste antes de que se ejecutara su línea de declaración.
Se registra antes con undefined — por qué no usar var
En artículos y librerías antiguos vas a ver variables declaradas con var, y si copias código que funciona, puede que var se cuele con él. Pero var se diferencia de const y let en dos cosas: dónde es válida la variable y qué obtienes si la lees antes de su declaración.
Una declaración var también se registra antes, pero al mismo tiempo se le guarda undefined. Por eso, si la referencias en una línea que está arriba de la declaración, no hay error: la ejecución sigue adelante con undefined. Además, no se limita a las llaves: es válida en toda la función que la contiene.
function checkStock(orderCount) {
// Leerla antes de la declaración no detiene nada; contiene undefined
console.log(stock); // undefined
var stock = 12;
console.log(stock); // 12
if (orderCount <= stock) {
var status = "Listo para enviar"; // Declarada con var
let note = "Enviar desde el almacén A"; // Declarada con let
}
// var es válida en toda la función, así que se puede leer fuera del if
console.log(status); // Listo para enviar
// console.log(note); // ReferenceError: note is not defined
}
checkStock(3);
if, el valor de un var se puede leer en las líneas que siguen al cierre de las llaves. Con let, la ejecución se detiene en la línea que lo lee fuera de su zona.Una función tiene una sola variable por cada nombre declarado con var, así que volver a declarar el mismo nombre con var dentro de un if no produce shadowing: sobrescribe la misma variable. La tabla de abajo muestra, para cada forma de declarar, qué se guarda cuando el nombre se registra antes de ejecutar el código y qué obtienes si lo lees en una línea que está arriba de la declaración.
| Declaración | Qué se registra antes de ejecutar | Si se lee arriba de la declaración |
|---|---|---|
| var stock = 12 | El nombre y undefined | Sigue adelante con undefined |
| let stock = 12 | Solo el nombre (sin valor) | Se detiene con ReferenceError |
| const stock = 12 | Solo el nombre (sin valor) | Se detiene con ReferenceError |
Al cambiar var por let, algunas líneas se detienen
Cuando reemplazas var por let en código antiguo, las líneas que leían la variable antes de su declaración, o fuera del if, se detienen con un ReferenceError. Esas líneas en realidad estaban leyendo undefined o un valor de otras llaves. Para corregirlas, mueve la declaración arriba de la línea que la lee, dentro de unas llaves que también rodeen esa línea.
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Cerca del inicio del archivo defines una función show que devuelve rate. Luego la llamas en una línea debajo de let rate = 0.1;. ¿Qué devuelve?
Pregunta 3Si escribes console.log(count); en una línea arriba de var count = 3;, ¿qué se muestra?