La lliçó anterior va acabar amb una pregunta oberta: quan crearTasca() fa ultimId = ultimId + 1, està tocant una variable que viu fora d'ella. Per què la pot veure? I es podria tenir un comptador d'identificadors que ningú més no pogués tocar per accident, sense deixar-lo solt al fitxer? Respondre-ho exigeix entendre dos conceptes que són entre els més importants de JavaScript: l'àmbit (on viu i des d'on es veu cada variable) i els closures (la capacitat d'una funció de recordar l'entorn on va néixer). Amb ells podràs crear estat privat, fàbriques de funcions configurades i memòries cau de resultats, tres eines que faràs servir a Nómada Tasques fins al final del curs.
Contingut
- Què és l'àmbit
- Àmbit global
- Àmbit de funció
- Àmbit de bloc:
leticonstdavant devar - La cadena d'àmbits, pas a pas
- Ombrejat de variables (shadowing)
- Què és un closure
- El closure explicat amb un exemple mínim
- El clàssic bucle amb
vardavant delet - Patró 1: generador d'identificadors
- Patró 2: fàbrica de funcions configurades
- Patró 3: estat privat amb el patró mòdul
- Patró 4: memoïtzació senzilla
- El cost en memòria
- Errors Habituals i Consells
- Exercicis
- Conclusió
- Què és l'àmbit
L'àmbit (scope) d'una variable és la regió del codi des de la qual se'n pot fer servir el nom. No és un detall acadèmic: és el que determina si una línia funciona o llança ReferenceError.
JavaScript té tres tipus d'àmbit:
| Àmbit | Es crea amb | Viu mentre | El declaren |
|---|---|---|---|
| Global | El fitxer o el <script> |
Duri el programa | const/let/var fora de tot |
| De funció | Cada crida a una funció | Duri la crida | Paràmetres i qualsevol declaració interna |
| De bloc | Cada { } (if, for, bloc solt) |
Duri el bloc | Només const i let (no var) |
I una regla que governa tota la resta:
Des de dins es veu cap a fora; des de fora no es veu cap a dins.
- Àmbit global
Tot el que es declara fora de qualsevol funció i de qualsevol bloc és global: visible des de qualsevol punt del fitxer.
'use strict';
const AVUI = '2026-09-20'; // global: constant de configuració del projecte
const LIMIT_SETMANAL = 40; // global: regla R7
function margeDe(horesObertes) {
return LIMIT_SETMANAL - horesObertes; // ✓ veu la global
}
console.log(margeDe(25)); // 15Les globals són útils per a constants de configuració i perilloses per a tota la resta:
| Risc | Explicació |
|---|---|
| Col·lisions de nom | Dos fitxers que declaren tasques es trepitgen |
| Acoblament invisible | Una funció que llegeix una global no diu a la seva signatura de què depèn |
| Impossible de provar | No la pots executar amb altres valors sense modificar la global |
| Mutació des de qualsevol lloc | Qualsevol línia del programa la pot canviar |
Regla pràctica: globals només en const i només per a configuració. Tot el que sigui variable, dins de funcions o protegit per un closure, com veuràs a l'apartat 12.
- Àmbit de funció
Cada crida a una funció crea un àmbit nou. Els paràmetres i les variables declarades al seu cos existeixen allà i desapareixen en acabar.
function resumirResponsable(nom, hores) {
const limit = 40; // local
const marge = limit - hores; // local
return `${nom}: marge de ${marge} h`;
}
console.log(resumirResponsable('Iván', 25)); // Iván: marge de 15 h
console.log(limit); // ✗ ReferenceError: limit is not definedI una cosa important: dues crides no comparteixen res.
function comptarDesDeZero() {
let comptador = 0; // neix de nou a cada crida
comptador++;
return comptador;
}
console.log(comptarDesDeZero()); // 1
console.log(comptarDesDeZero()); // 1 ← no recorda resAquesta amnèsia és el que els closures de l'apartat 7 permeten trencar quan convé.
- Àmbit de bloc:
let i const davant de var
let i const davant de varUn bloc és qualsevol parell de claus: el cos d'un if, d'un for, d'un while o unes claus soltes. let i const es queden dins del bloc; var no.
function analitzarTasca(estat, hores) {
if (estat === 'feta') {
const missatge = 'Tasca completada'; // només dins de l'if
var registre = 'feta'; // s'escapa a tota la funció!
}
console.log(registre); // 'feta' ← var ignora el bloc
console.log(missatge); // ✗ ReferenceError
}La comparació completa de les tres maneres de declarar:
var |
let |
const |
|
|---|---|---|---|
| Àmbit | Funció | Bloc | Bloc |
| Es pot reassignar | Sí | Sí | No |
| Es pot redeclarar al mateix àmbit | Sí (perillós) | No | No |
| Abans de la línia de declaració | undefined |
Error (TDZ) | Error (TDZ) |
S'afegeix a globalThis si és global |
Sí | No | No |
| Recomanació | No fer-la servir | Quan canviï | Per defecte |
Aquest "es pot redeclarar" de var és especialment traïdor:
var responsable = 'Iván';
var responsable = 'Marta'; // ✓ sense error: has perdut el valor anterior sense adonar-te
let revisor = 'Lucía';
let revisor = 'Marta'; // ✗ SyntaxError: Identifier 'revisor' has already been declaredEn aquest curs no faràs servir var. Apareix aquí només perquè el veuràs en codi antic i perquè el seu comportament explica l'exercici clàssic de l'apartat 9.
- La cadena d'àmbits, pas a pas
Quan el motor troba un nom, el busca a l'àmbit actual. Si no hi és, puja a l'àmbit que el conté. I així successivament fins al global. Si tampoc no hi és: ReferenceError.
'use strict';
const AVUI = '2026-09-20'; // ── Àmbit global
function generarInforme(responsable) { // ── Àmbit de generarInforme
const capcalera = `Informe de ${responsable}`;
function detallar(hores) { // ── Àmbit de detallar
const linia = `${hores} h`;
return `${capcalera} · ${linia} · a ${AVUI}`;
// ↑ àmbit pare ↑ local ↑ global
}
return detallar(25);
}
console.log(generarInforme('Iván'));
// Informe de Iván · 25 h · a 2026-09-20Així busca el motor cada nom dins de detallar:
flowchart TD
A["Àmbit de detallar<br/>linia, hores"] -->|no troba 'capcalera'| B["Àmbit de generarInforme<br/>capcalera, responsable"]
B -->|no troba 'AVUI'| C["Àmbit global<br/>AVUI, generarInforme"]
C -->|no troba res| D["ReferenceError"]
Tres conseqüències que convé fixar:
- La cerca és unidireccional: cap amunt. Des de
generarInformeno es pot veurelinia. - S'atura a la primera coincidència. Si
detallardeclarés la seva pròpiacapcalera, la cerca s'aturaria allà (apartat 6). - La cadena es decideix en escriure el codi, no en executar-lo. Això s'anomena àmbit lèxic, i és la clau per entendre els closures: el que importa és on està escrita la funció, no des d'on es crida.
- Ombrejat de variables (shadowing)
Quan un àmbit intern declara una variable amb el mateix nom que una d'externa, la interna tapa l'externa dins de la seva regió.
const estat = 'global';
function inspeccionar() {
const estat = 'de funció';
if (true) {
const estat = 'de bloc';
console.log(estat); // de bloc
}
console.log(estat); // de funció
}
inspeccionar();
console.log(estat); // globalL'ombrejat és legal i de vegades útil (un paràmetre anomenat tasca dins d'una funció que treballa amb tasques), però també és una font de confusió. Un cas real que mossega:
const LIMIT_SETMANAL = 40;
function comprovarCarrega(hores, LIMIT_SETMANAL) { // ✗ el paràmetre ombreja la constant
return hores > LIMIT_SETMANAL;
}
console.log(comprovarCarrega(45)); // false ← LIMIT_SETMANAL és undefined a dinsQui llegeix el cos assumeix que LIMIT_SETMANAL val 40, però a dins val undefined perquè no s'ha passat el segon argument, i 45 > undefined és false. Regles per evitar-ho:
- No reutilitzis noms de constants globals com a paràmetres.
- Si el linter avisa de
no-shadow, fes-li cas (08-02). - Quan l'ombrejat sigui deliberat, que es noti:
tascaa fora itascaOriginala dins, per exemple.
- Què és un closure
Un closure (o clausura) és una funció juntament amb l'entorn de variables en què va ser creada. Dit d'una altra manera:
Quan una funció es defineix dins d'una altra, recorda les variables de la funció exterior, fins i tot després que aquesta funció exterior hagi acabat.
La part sorprenent és l'última. Ja saps que les variables d'una funció desapareixen en acabar la crida. Però si una funció interna les continua fent servir, no desapareixen: el motor les manté vives tot el temps que calgui.
flowchart LR
subgraph E["Entorn capturat (sobreviu)"]
V["comptador = 0"]
end
F["funció interna<br/>retornada a l'exterior"] --> V
C["qui la crida<br/>des de fora"] --> F
- El closure explicat amb un exemple mínim
Comencem pel cas més petit possible:
function crearSalutacio(nom) {
const salutacio = `Hola, ${nom}`; // viu a l'àmbit de crearSalutacio
return function () { // funció interna
return salutacio; // fa servir una variable de l'àmbit pare
};
}
const saludarMarta = crearSalutacio('Marta');
// crearSalutacio JA ha acabat... i tanmateix:
console.log(saludarMarta()); // Hola, MartaLlegeix bé la seqüència:
- Es crida
crearSalutacio('Marta')i es crea un àmbit ambnomisalutacio. - Es crea la funció interna, que referencia
salutacio. crearSalutacioacaba i retorna aquesta funció.- Normalment
salutaciodesapareixeria. Però com que la funció retornada la necessita, el motor conserva l'entorn. - En cridar
saludarMarta(), la funció troba la sevasalutaciointacta.
I cada crida a crearSalutacio crea un entorn independent:
const saludarIvan = crearSalutacio('Iván');
const saludarLucia = crearSalutacio('Lucía');
console.log(saludarIvan()); // Hola, Iván
console.log(saludarLucia()); // Hola, LucíaDues funcions idèntiques en el seu codi, amb memòries diferents. Aquest és el mecanisme complet. Tot el que ve després són aplicacions d'aquesta mateixa idea.
- El clàssic bucle amb
var davant de let
var davant de letAquest exercici apareix a totes les entrevistes de feina i, més important, explica un error real que veuràs al Mòdul 6 en assignar gestors d'esdeveniments dins d'un bucle.
const titols = ['Redissenyar la sala', 'Cartelleria', 'Web de reserves'];
const accions = [];
for (var i = 0; i < titols.length; i++) {
accions.push(function () {
console.log(`${i}: ${titols[i]}`);
});
}
accions[0](); // 3: undefined
accions[1](); // 3: undefined
accions[2](); // 3: undefinedPer què? Perquè var i té àmbit de funció, no de bloc: hi ha una sola i per a tot el bucle. Les tres funcions capturen la mateixa variable, i quan s'executen (després del bucle) aquesta i ja val 3.
Amb let, cada iteració crea la seva pròpia i:
for (let i = 0; i < titols.length; i++) {
accions.push(function () {
console.log(`${i}: ${titols[i]}`);
});
}
accions[0](); // 0: Redissenyar la sala
accions[1](); // 1: Cartelleria
accions[2](); // 2: Web de reservesflowchart TD
subgraph VAR["Amb var: una sola variable"]
A1["fn 0"] --> I["i = 3"]
A2["fn 1"] --> I
A3["fn 2"] --> I
end
subgraph LET["Amb let: una per iteració"]
B1["fn 0"] --> J1["i = 0"]
B2["fn 1"] --> J2["i = 1"]
B3["fn 2"] --> J3["i = 2"]
end
Abans de let, la solució era embolcallar cada iteració en una IIFE —el patró que vas veure a Expressions de Funció i Funcions Fletxa— precisament per crear un àmbit nou a cada volta:
for (var i = 0; i < titols.length; i++) {
(function (index) {
accions.push(function () { console.log(`${index}: ${titols[index]}`); });
})(i);
}Avui n'hi ha prou d'escriure let. Però ara ja saps per què.
- Patró 1: generador d'identificadors
Tornem al cap solt de la lliçó anterior. ultimId estava solta al fitxer, visible i modificable per qualsevol. Amb un closure, deixa d'existir per a la resta del programa.
'use strict';
/**
* Retorna una funció que produeix identificadors correlatius (R1).
* El comptador queda tancat: ningú de fora no el pot llegir ni canviar.
*/
function crearGeneradorIds(inicial = 0) {
let ultimId = inicial;
return function seguentId() {
ultimId = ultimId + 1;
return ultimId;
};
}
const seguentId = crearGeneradorIds(6); // el backlog ja té 6 tasques
console.log(seguentId()); // 7
console.log(seguentId()); // 8
console.log(seguentId()); // 9
console.log(typeof ultimId); // 'undefined' ← protegidaCompara les dues versions:
Variable global ultimId |
Closure crearGeneradorIds |
|
|---|---|---|
| Qui la pot modificar | Qualsevol línia del programa | Només la funció retornada |
| Risc que un altre fitxer la trepitgi | Alt | Cap |
| Diversos generadors independents | Impossible | crearGeneradorIds() un altre cop |
| Valor inicial configurable | Editant el fitxer | Per paràmetre |
Aquest darrer punt és molt útil a les proves: al Mòdul 8 podràs crear un generador que comenci a 0 per a cada test, sense que un test contamini el següent.
const idsDeProva = crearGeneradorIds();
console.log(idsDeProva()); // 1 ← independent del generador de producció
console.log(seguentId()); // 10 ← l'altre continua pel seu compte
- Patró 2: fàbrica de funcions configurades
Una fàbrica és una funció que retorna una altra funció ja configurada amb uns paràmetres fixos. Evita repetir aquests paràmetres a cada crida.
const responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const titols = [
'Redissenyar la sala polivalent',
'Cartelleria del taller de serigrafia',
'Actualitzar el web de reserves',
'Inventari de tintes de serigrafia',
'Guia sobre enquadernació per a residents',
'Pressupost de la fusteria'
];
const hores = [12, 6, 14, 3, 8, 5];
const estats = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];
/**
* Retorna una funció que diu si la tasca de l'índex i pertany a `nom`.
* `nom` queda capturat al closure.
*/
function crearFiltrePerResponsable(nom) {
return function (index) {
return responsables[index] === nom;
};
}
const esDeIvan = crearFiltrePerResponsable('Iván');
const esDeMarta = crearFiltrePerResponsable('Marta');
console.log(esDeIvan(0)); // true
console.log(esDeIvan(1)); // false
console.log(esDeMarta(1)); // trueI així es fa servir per recórrer el backlog amb criteris intercanviables:
function llistarSegons(filtre) {
const trobades = [];
for (let i = 0; i < titols.length; i++) {
if (filtre(i)) trobades.push(titols[i]);
}
return trobades;
}
console.log(llistarSegons(esDeIvan));
// [ 'Redissenyar la sala polivalent',
// 'Guia sobre enquadernació per a residents',
// 'Pressupost de la fusteria' ]
// I les fàbriques es poden combinar
function crearFiltrePerEstat(estat) {
return (index) => estats[index] === estat;
}
function crearFiltreCombinat(filtreA, filtreB) {
return (index) => filtreA(index) && filtreB(index);
}
const pendentsDeIvan = crearFiltreCombinat(esDeIvan, crearFiltrePerEstat('pendent'));
console.log(llistarSegons(pendentsDeIvan));
// [ 'Pressupost de la fusteria' ]Fixa't en el que acaba de passar: llistarSegons no sap res de responsables ni d'estats; només sap cridar una funció que li diuen. Això és una funció d'ordre superior, i és el tema complet de Funcions d'Ordre Superior.
- Patró 3: estat privat amb el patró mòdul
El patró mòdul combina un closure amb un objecte de retorn: les variables queden privades i només s'exposa un conjunt controlat de funcions.
'use strict';
function crearMagatzemTasques(idInicial = 0) {
// ── Estat privat: inaccessible des de fora ───────────────────
const tasques = [];
let ultimId = idInicial;
// ── Funció auxiliar privada ──────────────────────────────────
function cercarIndex(id) {
for (let i = 0; i < tasques.length; i++) {
if (tasques[i].id === id) return i;
}
return -1;
}
// ── Interfície pública ───────────────────────────────────────
return {
afegir: function (titol, responsable, horesEstimades) {
ultimId = ultimId + 1;
tasques.push({
id: ultimId,
titol: titol,
responsable: responsable ?? null,
estat: 'pendent',
horesEstimades: horesEstimades
});
return ultimId;
},
canviarEstat: function (id, nouEstat) {
const i = cercarIndex(id);
if (i === -1) throw new Error(`No existeix la tasca ${id}.`);
tasques[i].estat = nouEstat;
return true;
},
comptar: function () {
return tasques.length;
},
horesObertes: function () {
let total = 0;
for (const t of tasques) {
if (t.estat !== 'feta') total += t.horesEstimades;
}
return total;
}
};
}
const magatzem = crearMagatzemTasques(6);
console.log(magatzem.afegir('Renovar el torn', 'Lucía', 10)); // 7
console.log(magatzem.afegir('Comprar tintes', null, 2)); // 8
console.log(magatzem.comptar()); // 2
console.log(magatzem.horesObertes()); // 12
magatzem.canviarEstat(7, 'feta');
console.log(magatzem.horesObertes()); // 2
console.log(magatzem.tasques); // undefined ← protegit
console.log(typeof cercarIndex); // 'undefined' ← privadaLes garanties que ofereix aquesta estructura són reals, no d'estil:
- Ningú no pot fer
magatzem.tasques.push(escombraries), perquètasquesno està exposat. ultimIdno es pot alterar, així que la R1 (identificadors únics i correlatius) està garantida per construcció.cercarIndexés un detall intern: es pot reescriure sense trencar res a ningú.
Al Mòdul 5 veuràs dues evolucions d'aquesta idea: les classes amb camps privats i els mòduls ES, que aconsegueixen el mateix a escala de fitxer. El patró mòdul amb closures és l'antecessor de tots dos i continua sent perfectament vàlid.
- Patró 4: memoïtzació senzilla
Memoïtzar és desar el resultat d'una funció per no haver-lo de tornar a calcular amb els mateixos arguments. El magatzem de resultats viu en un closure.
/**
* Compta les tasques d'un responsable, desant a la memòria cau cada resultat.
* Només és correcte perquè comptar és una funció PURA (vegeu 03-03).
*/
function crearComptadorMemoitzat(responsables) {
const cache = {}; // privat: la memòria de la funció
let calculs = 0;
let encerts = 0;
const comptar = function (nom) {
if (nom in cache) {
encerts++;
return cache[nom];
}
calculs++;
let total = 0;
for (const r of responsables) {
if (r === nom) total++;
}
cache[nom] = total;
return total;
};
comptar.estadistiques = () => `${calculs} càlculs, ${encerts} encerts de memòria cau`;
return comptar;
}
const comptarTasquesDe = crearComptadorMemoitzat(responsables);
console.log(comptarTasquesDe('Iván')); // 3 (calculat)
console.log(comptarTasquesDe('Iván')); // 3 (de memòria cau)
console.log(comptarTasquesDe('Marta')); // 2 (calculat)
console.log(comptarTasquesDe('Iván')); // 3 (de memòria cau)
console.log(comptarTasquesDe.estadistiques()); // 2 càlculs, 2 encerts de memòria cauDos avisos importants sobre la memoïtzació:
- Només és vàlida amb funcions pures. Si el resultat depèn d'alguna cosa que canvia (el rellotge, una variable externa, el contingut del backlog), la memòria cau retornarà dades obsoletes. Per això 03-03 insistia tant en la puresa.
- La memòria cau creix. Cada argument nou hi afegeix una entrada que no s'allibera mai mentre la funció visqui.
A Recursivitat aplicaràs exactament aquesta tècnica a Fibonacci, on la diferència de rendiment és espectacular.
- El cost en memòria
Un closure manté viu tot l'entorn de la funció exterior mentre la funció interna existeixi. Això és potent i té preu:
function crearFormatadorPesat() {
const backlogComplet = carregarMilersDeTasques(); // imagina 50 MB de dades
const separador = ' · ';
return function (titol, hores) {
return titol + separador + hores; // només fa servir `separador`
};
}
const formatar = crearFormatadorPesat();
// `backlogComplet` pot continuar en memòria mentre `formatar` existeixiEls motors moderns optimitzen força bé aquest cas i solen alliberar el que la funció interna demostradament no fa servir, però no convé dependre'n. Bons hàbits:
| Pràctica | Motiu |
|---|---|
| Capturar només el necessari (extreure la dada abans de tancar-hi a sobre) | Redueix el que queda retingut |
Posar a null les referències grans quan ja no calguin |
Permet alliberar-les |
| Limitar la mida de les memòries cau de memoïtzació | Evita creixement sense fi |
| No crear closures dins de bucles molt llargs si no cal | Cadascun és un entorn viu |
Això connecta directament amb les fuites de memòria de Gestió de Memòria, on veuràs com detectar-les amb les DevTools. Per ara n'hi ha prou amb la idea: un closure no és gratis, però en el 99 % dels casos el seu cost és irrellevant comparat amb el que aporta.
Errors Habituals i Consells
1. Creure que un closure copia el valor. No el copia: manté una referència viva a la variable.
function crearEtiquetador() {
let prefix = 'TN';
const etiquetar = (id) => `${prefix}-${id}`;
prefix = 'NOMADA'; // es canvia DESPRÉS de crear la funció
return etiquetar;
}
console.log(crearEtiquetador()(7)); // NOMADA-7 ← veu el valor actual, no el d'aleshores2. Compartir estat sense adonar-se'n. Dues funcions creades a la mateixa crida comparteixen entorn:
function crearComptadorDoble() {
let n = 0;
return { pujar: () => ++n, llegir: () => n };
}
const c = crearComptadorDoble();
c.pujar(); c.pujar();
console.log(c.llegir()); // 2 ← comparteixen la mateixa `n`, que és el que volem aquíÉs desitjable quan el busques i un error quan no.
3. Fer servir var en un bucle que crea funcions. El cas de l'apartat 9. Amb let desapareix.
4. Ombrejar una constant global amb un paràmetre. Dóna resultats false o undefined sense cap error.
5. Memoïtzar una funció impura. Retornarà dades caducades. Abans de memoïtzar, comprova que la funció és pura.
6. Pensar que l'àmbit depèn de qui crida. No: depèn d'on està escrita la funció (àmbit lèxic). Aquest és el punt que més costa i el que fa predictibles els closures.
const etiqueta = 'global';
function mostrar() {
console.log(etiqueta); // sempre 'global'
}
function cridar() {
const etiqueta = 'local'; // NO afecta mostrar()
mostrar();
}
cridar(); // global7. Consell: si una funció necessita recordar alguna cosa entre crides, pensa en un closure abans que en una variable global. És la diferència entre estat controlat i estat solt.
8. Consell: anomena les fàbriques amb el prefix crear…. crearGeneradorIds, crearFiltrePerResponsable, crearMagatzemTasques. El nom avisa que el que retorna no és una dada, sinó una eina.
Exercicis
Exercici 1 — Predir la sortida
Sense executar el codi, escriu què imprimeix cada console.log i explica per què.
const etiqueta = 'A';
function externa() {
const etiqueta = 'B';
function interna() {
console.log(etiqueta); // (1)
}
if (true) {
const etiqueta = 'C';
console.log(etiqueta); // (2)
interna(); // (3)
}
interna(); // (4)
}
externa();
console.log(etiqueta); // (5)
function comptadorTrencat() {
var n = 0;
const fns = [];
for (var i = 0; i < 3; i++) {
fns.push(() => i + n);
}
return fns;
}
const fns = comptadorTrencat();
console.log(fns[0](), fns[1](), fns[2]()); // (6)Exercici 2 — crearComptadorEstat
Escriu una fàbrica crearComptadorEstat(estats) que rebi l'array d'estats del backlog i retorni un objecte amb tres funcions: registrar(estat) (suma un al recompte d'aquest estat), recompte(estat) (retorna quantes vegades s'ha registrat) i resum() (retorna una cadena amb els tres estats i els seus recomptes). El recompte ha de ser privat. Inicialitza'l recorrent l'array rebut.
Exercici 3 — Memoïtzar calcularEsforc
Escriu memoitzar(funcio), una fàbrica genèrica que rebi una funció d'un paràmetre de tipus cadena o nombre i retorni una versió memoïtzada. Aplica-la a una funció calcularEsforc(prioritat) que sigui deliberadament costosa (fes-hi un bucle d'un milió de voltes a dins) i mesura amb Date.now() la diferència entre la primera i la segona crida.
Solucions
Exercici 1
| Núm. | Sortida | Explicació |
|---|---|---|
| (2) | C |
El const del bloc if ombreja el de la funció |
| (3) | B |
Àmbit lèxic: interna està escrita dins d'externa, així que veu l'etiqueta d'externa. Que es cridi des del bloc és irrellevant |
| (4) | B |
Mateixa raó |
| (1) | — | És la línia que produeix (3) i (4); no s'executa tota sola |
| (5) | A |
La global no va ser tocada mai |
| (6) | 3 3 3 |
var i és única per a tota la funció; en executar les fletxes, i ja val 3 i n val 0 |
Ordre real d'impressió: C, B, B, A, 3 3 3.
Per arreglar (6) n'hi ha prou amb canviar var i per let i, i aleshores donaria 0 1 2.
Exercici 2
function crearComptadorEstat(estatsInicials = []) {
// Estat privat
const recomptes = { pendent: 0, 'en-curs': 0, feta: 0 };
function sumar(estat) {
if (!(estat in recomptes)) {
throw new Error(`Estat desconegut: ${estat}`);
}
recomptes[estat] = recomptes[estat] + 1;
}
// Inicialització a partir de l'array rebut
for (const e of estatsInicials) {
sumar(e);
}
return {
registrar: (estat) => { sumar(estat); },
recompte: (estat) => recomptes[estat] ?? 0,
resum: () =>
`pendent: ${recomptes.pendent} · en curs: ${recomptes['en-curs']} · feta: ${recomptes.feta}`
};
}
const estats = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];
const comptador = crearComptadorEstat(estats);
console.log(comptador.resum()); // pendent: 3 · en curs: 2 · feta: 1
console.log(comptador.recompte('feta')); // 1
comptador.registrar('feta');
console.log(comptador.resum()); // pendent: 3 · en curs: 2 · feta: 2
console.log(comptador.recomptes); // undefined ← privat
comptador.registrar('arxivada'); // ✗ Error: Estat desconegut: arxivadaComentari: recomptes i sumar són privats; la interfície pública només permet operacions vàlides. Ningú no pot escriure comptador.recomptes.feta = 99 perquè recomptes no existeix a fora. I el throw manté l'estat sempre coherent, aplicant el principi de fallar aviat de Gestió d'Errors.
Exercici 3
function memoitzar(funcio) {
const cache = {};
return function (argument) {
const clau = String(argument);
if (clau in cache) return cache[clau];
const resultat = funcio(argument);
cache[clau] = resultat;
return resultat;
};
}
// Funció deliberadament costosa
function calcularEsforc(prioritat) {
let acumulat = 0;
for (let i = 0; i < 20000000; i++) {
acumulat += i % 3;
}
if (prioritat === 'alta') return 3 + (acumulat % 1);
if (prioritat === 'mitjana') return 2 + (acumulat % 1);
return 1 + (acumulat % 1);
}
const esforcRapid = memoitzar(calcularEsforc);
let t = Date.now();
console.log(esforcRapid('alta'), `${Date.now() - t} ms`); // 3 ~120 ms
t = Date.now();
console.log(esforcRapid('alta'), `${Date.now() - t} ms`); // 3 0 ms
t = Date.now();
console.log(esforcRapid('mitjana'), `${Date.now() - t} ms`); // 2 ~120 msComentari: la segona crida amb 'alta' és pràcticament instantània perquè no executa el bucle. String(argument) normalitza la clau perquè 3 i '3' comparteixin entrada (una cosa que en aquest cas volem, però que en d'altres podria ser un problema: és la limitació de fer servir un objecte com a memòria cau amb claus de text). La restricció a un paràmetre no és casual: memoïtzar amb diversos exigeix serialitzar tots els arguments en una clau, una cosa que resoldràs amb JSON.stringify a JSON i Còpies d'Objectes.
Conclusió
Ja saps on viu cada variable. L'àmbit global és per a constants de configuració com AVUI i LIMIT_SETMANAL; l'àmbit de funció neix i mor amb cada crida; i l'àmbit de bloc, que només respecten let i const, manté les variables dins de les seves claus. La cadena d'àmbits busca cada nom de dins cap a fora i s'atura a la primera coincidència, cosa que explica l'ombrejat i l'error clàssic del paràmetre que tapa una constant global. I sobretot saps que l'àmbit és lèxic: depèn d'on està escrita la funció, no de qui la crida.
D'aquí en surt el concepte central de la lliçó. Un closure és una funció que conserva viu l'entorn on va néixer, i amb ell has construït quatre eines que Nómada Tasques farà servir fins al final: crearGeneradorIds(), que garanteix la R1 sense deixar cap comptador solt; crearFiltrePerResponsable(), una fàbrica que produeix filtres ja configurats i combinables; crearMagatzemTasques(), amb estat veritablement privat i una interfície pública controlada; i crearComptadorMemoitzat(), que desa a la memòria cau resultats de funcions pures. També entens per fi el bucle amb var que retorna sempre l'últim índex, i saps que un closure reté memòria, un cost que s'estudia en profunditat a Gestió de Memòria.
Però en aquesta lliçó has donat per fet una cosa que encara no s'ha explicat: que el motor "sap" quines variables existeixen a cada àmbit abans d'executar res. És el que permet cridar una declaració de funció abans de la seva línia, el que fa que var valgui undefined en lloc de donar error, i el que fa que let i const llancin aquest misteriós Cannot access before initialization que ja has vist tres vegades. Tot això és la feina de preparació que el motor fa a cada àmbit, i és el tema de Hoisting i el Context d'Execució, on a més desmuntaràs la pila de crides que a 03-01 només vas veure per sobre.
Curs de JavaScript: De Principiant a Avançat
Mòdul 1: Introducció a JavaScript
- Què és JavaScript?
- Configuració del teu Entorn de Desenvolupament
- El teu Primer Programa en JavaScript
- Sintaxi i Conceptes Bàsics de JavaScript
- Variables i Tipus de Dades
- Operadors Bàsics
- Conversió de Tipus i Comparacions
- El Projecte del Curs: Nómada Tasques
Mòdul 2: Estructures de Control
- Sentències Condicionals
- Bucles: for, while, do-while
- Sentències Switch
- Control del Flux: break, continue i Bucles Imbricats
- Gestió d'Errors amb try-catch
Mòdul 3: Funcions
- Definició i Crida de Funcions
- Expressions de Funció i Funcions Fletxa
- Paràmetres i Valors de Retorn
- Àmbit i Closures
- Hoisting i el Context d'Execució
- Funcions d'Ordre Superior
- Recursivitat
Mòdul 4: Objectes i Arrays
- Introducció als Objectes
- Mètodes d'Objecte i la Paraula Clau
this - Arrays: Conceptes Bàsics i Mètodes
- Iteració sobre Arrays
- Cercar, Ordenar i Agregar Dades: find, sort i reduce
- Desestructuració d'Arrays
- Desestructuració d'Objectes, Spread i Rest
- JSON i Còpies d'Objectes
Mòdul 5: Objectes i Funcions Avançades
- Prototips i Herència
- Classes i Programació Orientada a Objectes
- Encapsulació: Getters, Setters i Camps Privats
- Mòduls i Importació/Exportació
- JavaScript Asíncron: Callbacks
- Promeses i Async/Await
- El Bucle d'Esdeveniments i la Cua de Microtasques
- Iteradors i Generadors
Mòdul 6: El Model d'Objectes del Document (DOM)
- Introducció al DOM
- Selecció i Manipulació d'Elements del DOM
- Gestió d'Esdeveniments
- Propagació, Delegació i Esdeveniments Personalitzats
- Creació i Eliminació d'Elements del DOM
- Renderitzat de Llistes i Plantilles HTML
- Gestió i Validació de Formularis
Mòdul 7: APIs del Navegador i Temes Avançats
- Emmagatzematge Local i de Sessió
- Fetch API i AJAX
- Peticions Robustes: Errors, Timeouts i AbortController
- WebSockets
- Service Workers i Aplicacions Web Progressives (PWAs)
- APIs del Navegador Essencials
- Introducció a WebAssembly
Mòdul 8: Proves i Depuració
- Depuració de JavaScript
- Qualitat de Codi: ESLint, Prettier i Convencions
- Proves Unitàries amb Jest
- Dobles de Prova: Mocks, Stubs i Spies
- Proves d'Integració
- Proves d'Extrem a Extrem amb Cypress
Mòdul 9: Rendiment i Optimització
- Mesurar Abans d'Optimitzar: DevTools i Web Vitals
- Optimització del Rendiment de JavaScript
- Gestió de Memòria
- Manipulació Eficient del DOM
- Càrrega Diferida i Divisió de Codi
Mòdul 10: Frameworks i Llibreries de JavaScript
- Per Què Existeixen els Frameworks
- Introducció a React
- Gestió d'Estat amb Redux
- Conceptes Bàsics de Vue.js
- Conceptes Bàsics d'Angular
- Triar el Framework Adequat
