A la lliçó anterior vas donar per fet una cosa que no s'havia explicat: que el motor "sap" quines variables existeixen a cada àmbit abans d'executar una sola línia. Aquesta preparació prèvia és el que permet cridar una declaració de funció abans d'escriure-la, el que fa que una var valgui undefined en lloc de donar error, i el que produeix el Cannot access 'x' before initialization que ja has vist tres vegades. En aquesta lliçó desmuntaràs aquest mecanisme: les dues fases per les quals passa cada àmbit, el hoisting, la zona morta temporal, el context d'execució i la pila de crides que a 03-01 només vas veure per sobre. L'objectiu pràctic és doble: entendre qualsevol traça d'error i ordenar el fitxer js/app.js de Nómada Tasques perquè res d'això no arribi a importar.

Contingut

  1. El motor no executa a la primera passada
  2. Fase de creació i fase d'execució
  3. Hoisting de declaracions de funció
  4. Hoisting de var
  5. let i const: la zona morta temporal
  6. Taula comparativa completa
  7. Traces comentades pas a pas
  8. El context d'execució global
  9. Els contextos de funció
  10. La pila de crides
  11. RangeError: Maximum call stack size exceeded
  12. Llegir un stack trace real
  13. Cas pràctic: ordenar js/app.js
  14. Per què l'estil modern fa irrellevant el hoisting
  15. Errors Habituals i Consells
  16. Exercicis
  17. Conclusió

  1. El motor no executa a la primera passada

Intuïtivament un s'imagina que JavaScript llegeix el fitxer línia a línia i va executant. No és així. Abans d'executar res d'un àmbit, el motor el recorre sencer per esbrinar quines declaracions conté i reservar-los lloc.

Aquest codi ho demostra:

console.log(pesDePrioritat('alta'));     // 3   ✓ funciona
console.log(AVUI);                       // ✗ ReferenceError: Cannot access 'AVUI' before initialization

function pesDePrioritat(prioritat) {
  return prioritat === 'alta' ? 3 : 1;
}

const AVUI = '2026-09-20';

Fixa't en l'asimetria: la funció es pot cridar abans; la constant, no. I —clau— l'error d'AVUI no és "no està definida", sinó "encara no s'hi pot accedir". El motor sap perfectament que AVUI existeix: simplement no deixa fer-la servir encara. Això només té sentit si hi va haver una passada prèvia.

  1. Fase de creació i fase d'execució

Cada vegada que el motor entra en un àmbit (el fitxer complet o el cos d'una funció), fa dues fases:

flowchart TD
    A["Entrar en un àmbit"] --> B["FASE DE CREACIÓ"]
    B --> B1["Registrar declaracions de funció<br/>completes i a punt per fer servir"]
    B --> B2["Registrar les var<br/>i inicialitzar-les a undefined"]
    B --> B3["Registrar let/const<br/>SENSE inicialitzar → TDZ"]
    B --> C["FASE D'EXECUCIÓ"]
    C --> C1["Recórrer les línies de dalt a baix"]
    C --> C2["Assignar valors"]
    C --> C3["Cridar funcions"]

Fase de creació (abans d'executar res):

Tipus de declaració Què es registra Valor inicial
function nom() {} El nom i la funció completa La funció, ja utilitzable
var x El nom undefined
let x / const x El nom Res: queda sense inicialitzar
class X {} El nom Res: TDZ, com let

Fase d'execució: es recorren les línies i es fan les assignacions reals.

El terme hoisting ("elevació") descriu l'efecte observable de la fase de creació: és com si les declaracions s'haguessin mogut al principi de l'àmbit. En realitat no es mou res; el motor simplement ja les coneixia.

  1. Hoisting de declaracions de funció

Les declaracions de funció s'eleven completes: nom i cos.

console.log(estaVencuda('2026-09-05', 'pendent', '2026-09-20'));   // true

function estaVencuda(dataLimit, estat, avui) {
  return dataLimit < avui && estat !== 'feta';
}

És el que a 03-01 es va anunciar de passada: les declaracions es poden cridar abans de la línia on estan escrites. El motor "veu" així el fitxer:

// --- Fase de creació ---
// estaVencuda = [funció completa]

// --- Fase d'execució ---
console.log(estaVencuda(...));   // ja existeix

Les expressions de funció i les fletxes no es comporten així, perquè el que s'eleva és la variable, no la funció:

console.log(typeof declarada);        // 'function'
console.log(typeof expressadaVar);    // 'undefined'   ← la var existeix, però val undefined
console.log(typeof expressadaConst);  // ✗ ReferenceError

function declarada() {}
var expressadaVar = function () {};
const expressadaConst = () => {};

Un cas que sorprèn: dins d'un bloc, en mode estricte, les declaracions de funció tenen àmbit de bloc.

'use strict';

if (true) {
  function auxiliar() { return 'dins'; }
}
console.log(typeof auxiliar);   // 'undefined' en mode estricte

Conclusió pràctica: no declaris funcions dins de blocs. Si necessites una funció condicional, fes servir una expressió assignada a una variable declarada a fora.

  1. Hoisting de var

Una var s'eleva sense el seu valor: existeix des del principi de l'àmbit de funció, valent undefined.

function calcularMarge(horesObertes) {
  console.log(limit);           // undefined  ← no dóna error, però tampoc no val res
  var limit = 40;
  console.log(limit);           // 40
  return limit - horesObertes;
}

calcularMarge(25);

És equivalent a haver escrit:

function calcularMarge(horesObertes) {
  var limit;                    // ← elevada per la fase de creació
  console.log(limit);           // undefined
  limit = 40;                   // ← l'assignació es queda on era
  console.log(limit);
  return limit - horesObertes;
}

Aquest comportament és l'origen d'una classe sencera de bugs silenciosos: el programa no falla, simplement calcula amb undefined i produeix NaN més endavant. Combinat amb l'àmbit de funció que vas veure a Àmbit i Closures, dóna lloc a situacions com aquesta:

function analitzar(estats) {
  for (var i = 0; i < estats.length; i++) {
    if (estats[i] === 'feta') {
      var completades = (completades ?? 0) + 1;   // funciona? sí, però és il·legible
    }
  }
  return completades;    // visible fora de l'if i del for: àmbit de funció
}

Amb let i const res d'això no passa, perquè l'àmbit és el bloc i l'accés anticipat dóna error.

  1. let i const: la zona morta temporal

let i const també s'eleven: el motor registra els seus noms a la fase de creació. El que no fan és inicialitzar-se. Queden en un estat especial anomenat zona morta temporal (Temporal Dead Zone, TDZ), que va des de l'inici de l'àmbit fins a la línia de la declaració.

{
  //  ┌─ inici de la TDZ de `prioritat`
  console.log(prioritat);      // ✗ ReferenceError: Cannot access 'prioritat' before initialization
  const prioritat = 'alta';
  //  └─ fi de la TDZ
  console.log(prioritat);      // 'alta'
}
flowchart LR
    A["Inici de l'àmbit"] -->|TDZ: accedir-hi llança ReferenceError| B["Línia de la declaració"]
    B -->|Inicialitzada| C["Resta de l'àmbit: ús normal"]

La TDZ no és un caprici: és una protecció. Converteix en error immediat el que amb var era un undefined silenciós. I demostra que l'elevació existeix, perquè el missatge distingeix dues situacions:

console.log(noDeclarada);      // ✗ ReferenceError: noDeclarada is not defined
console.log(enTdz);            // ✗ ReferenceError: Cannot access 'enTdz' before initialization
let enTdz = 1;

"is not defined" = el nom no existeix enlloc. "Cannot access before initialization" = el nom existeix, però encara és a la seva TDZ. Saber llegir aquesta diferència estalvia molt de temps depurant.

Un detall final: typeof no protegeix de la TDZ.

console.log(typeof inexistent);   // 'undefined'  ← segur
console.log(typeof enTdz);        // ✗ ReferenceError
let enTdz = 1;

  1. Taula comparativa completa

function f(){} var x let x const x
S'eleva el nom?
S'eleva el valor? Sí, complet No (undefined) No (TDZ) No (TDZ)
Ús abans de la línia Funciona undefined ReferenceError ReferenceError
Àmbit Bloc (estricte) Funció Bloc Bloc
Redeclarable No No
Reassignable No
Inicialització obligatòria No No
S'afegeix a globalThis (nivell superior) No No

  1. Traces comentades pas a pas

Analitzem un fitxer complet simulant el que fa el motor. Aquest és el codi:

'use strict';

console.log('1:', typeof pesDePrioritat);    // ?
console.log('2:', typeof formatar);          // ?
console.log('3:', comptadorTasques);         // ?
// console.log('4:', AVUI);                  // llançaria ReferenceError

var comptadorTasques = 0;
const AVUI = '2026-09-20';

function pesDePrioritat(prioritat) {
  return prioritat === 'alta' ? 3 : 1;
}

const formatar = (t) => t.toUpperCase();

console.log('5:', typeof formatar);          // ?
console.log('6:', comptadorTasques);         // ?
console.log('7:', AVUI);                     // ?

Fase de creació de l'àmbit global:

Nom Estat després de la fase de creació
pesDePrioritat Funció completa, a punt
comptadorTasques undefined (és var)
AVUI Sense inicialitzar (TDZ)
formatar Sense inicialitzar (TDZ)

Fase d'execució, línia a línia:

1: 'function'      → la declaració estava a punt des del principi
2: 'undefined'     → ✗ NO: formatar és a la TDZ; typeof LLANÇA ReferenceError

Aquí convé aturar-se, perquè l'exemple té un parany deliberat: la línia 2 no imprimeix 'undefined', llança un error, perquè typeof no protegeix de la TDZ. Corregint el fitxer perquè s'executi:

'use strict';

console.log('1:', typeof pesDePrioritat);    // 1: function
console.log('3:', comptadorTasques);         // 3: undefined

var comptadorTasques = 0;
const AVUI = '2026-09-20';

function pesDePrioritat(prioritat) {
  return prioritat === 'alta' ? 3 : 1;
}

const formatar = (t) => t.toUpperCase();

console.log('5:', typeof formatar);          // 5: function
console.log('6:', comptadorTasques);         // 6: 0
console.log('7:', AVUI);                     // 7: 2026-09-20

Sortida completa:

1: function
3: undefined
5: function
6: 0
7: 2026-09-20

La moralitat de l'exercici no és memoritzar la taula, sinó això: qualsevol fitxer que depengui d'aquestes subtileses està mal ordenat.

  1. El context d'execució global

Un context d'execució és l'estructura interna que el motor crea per executar un tros de codi. Conté tres coses:

Component Què desa
Entorn de variables Les variables i funcions declarades en aquest àmbit
Referència a l'entorn exterior La baula que forma la cadena d'àmbits de 03-04
Valor de this L'objecte de context (matèria de 04-02)

El primer que es crea és el context global, un per fitxer o script. Al navegador, el seu this de nivell superior és window (o undefined dins de funcions en mode estricte); a Node.js és module.exports o {} segons el tipus de mòdul.

Una conseqüència pràctica que es veu de seguida:

var ambVar = 'accessible des de window';
let ambLet = 'no accessible des de window';

// Al navegador, en un <script> clàssic:
console.log(window.ambVar);   // 'accessible des de window'
console.log(window.ambLet);   // undefined

Per això var a escala global és perillosa: contamina l'objecte global i pot trepitjar propietats existents. let i const no ho fan.

  1. Els contextos de funció

Cada crida —no cada funció, cada crida— crea un context nou amb les seves pròpies dues fases.

function resumirCarrega(responsable, hores) {
  //  Fase de creació d'AQUEST context:
  //    responsable = 'Iván', hores = 25   (els paràmetres ja vénen assignats)
  //    marge       = sense inicialitzar (TDZ)
  //    etiqueta    = sense inicialitzar (TDZ)

  const marge = 40 - hores;
  const etiqueta = marge < 0 ? 'SOBRECÀRREGA' : 'OK';
  return `${responsable}: ${marge} h de marge — ${etiqueta}`;
}

console.log(resumirCarrega('Iván', 25));    // Iván: 15 h de marge — OK
console.log(resumirCarrega('Lucía', 14));   // Lucía: 26 h de marge — OK

Les dues crides creen dos contextos completament independents, amb les seves pròpies marge i etiqueta, que es destrueixen en acabar. Aquest és exactament el mecanisme que explicava a 03-04 per què les variables locals no es comparteixen entre crides… tret que un closure les mantingui vives.

  1. La pila de crides

Els contextos s'apilen. La pila de crides (call stack) és l'estructura on el motor desa quins contextos són actius, amb la regla LIFO: l'últim a entrar és el primer a sortir.

'use strict';

function pesDePrioritat(prioritat) {
  if (prioritat === 'alta') return 3;
  if (prioritat === 'mitjana') return 2;
  return 1;
}

function esforcDeTasca(prioritat, hores) {
  return pesDePrioritat(prioritat) * hores;
}

function informeBacklog(prioritats, hores) {
  let total = 0;
  for (let i = 0; i < hores.length; i++) {
    total += esforcDeTasca(prioritats[i], hores[i]);
  }
  return total;
}

console.log(informeBacklog(
  ['alta', 'mitjana', 'alta', 'baixa', 'mitjana', 'alta'],
  [12, 6, 14, 3, 8, 5]
));   // 124
sequenceDiagram
    participant G as Context global
    participant I as informeBacklog
    participant E as esforcDeTasca
    participant P as pesDePrioritat

    G->>I: crida
    activate I
    loop 6 vegades
        I->>E: crida
        activate E
        E->>P: crida
        activate P
        P-->>E: retorna el pes
        deactivate P
        E-->>I: retorna pes × hores
        deactivate E
    end
    I-->>G: retorna 124
    deactivate I

Al punt més profund hi ha quatre contextos apilats: global → informeBacklogesforcDeTascapesDePrioritat. I només quatre: el bucle no acumula contextos, perquè cada crida es tanca abans de la següent.

Dues propietats importants de la pila:

  1. És d'un sol fil. JavaScript executa una cosa cada vegada. Mentre hi hagi res a la pila, res més no pot córrer: ni un temporitzador, ni un clic. Aquesta restricció és la raó de ser del bucle d'esdeveniments que estudiaràs a El Bucle d'Esdeveniments i les Microtasques.
  2. Té una mida limitada. I d'aquí surt l'error de l'apartat següent.

  1. RangeError: Maximum call stack size exceeded

Si les crides s'apilen sense que cap acabi, la pila s'omple:

function comptarTasquesRestants(n) {
  return comptarTasquesRestants(n - 1);   // no s'atura mai
}

comptarTasquesRestants(6);
// ✗ RangeError: Maximum call stack size exceeded

El límit ronda les 10 000-15 000 crides segons el motor. Les tres causes habituals:

Causa Exemple Solució
Recursió sense cas base El de dalt Afegir la condició d'aturada
Cas base inabastable if (n === 0) cridat amb n = 5.5 Fer servir n <= 0
Recursió mútua accidental a() crida b(), que crida a() Revisar el cicle

Pots comprovar el límit del teu entorn:

function mesurarProfunditat(n = 1) {
  try {
    return mesurarProfunditat(n + 1);
  } catch (error) {
    return n;
  }
}

console.log(`Profunditat màxima: ${mesurarProfunditat()}`);
// Profunditat màxima: 11373  (varia segons el motor i el navegador)

Aquest error és el senyal més freqüent d'una recursió mal escrita, i per això hi tornaràs a Recursivitat.

  1. Llegir un stack trace real

Quan alguna cosa falla, el stack d'un Error és una foto de la pila en aquell instant. Provoquem una fallada real:

'use strict';

function pesDePrioritat(prioritat) {
  return prioritat.toLowerCase() === 'alta' ? 3 : 1;   // ← peta si és null
}

function esforcDeTasca(prioritat, hores) {
  return pesDePrioritat(prioritat) * hores;
}

function informeBacklog(prioritats, hores) {
  let total = 0;
  for (let i = 0; i < hores.length; i++) {
    total += esforcDeTasca(prioritats[i], hores[i]);
  }
  return total;
}

console.log(informeBacklog(['alta', null], [12, 6]));

Traça a Node.js:

TypeError: Cannot read properties of null (reading 'toLowerCase')
    at pesDePrioritat (/nomada-tasques/js/app.js:4:20)
    at esforcDeTasca (/nomada-tasques/js/app.js:8:10)
    at informeBacklog (/nomada-tasques/js/app.js:14:14)
    at Object.<anonymous> (/nomada-tasques/js/app.js:19:13)

Com es llegeix, de dalt a baix:

Línia Significat
TypeError: Cannot read properties of null Què ha passat
at pesDePrioritat (…:4:20) On ha petat: fitxer, línia 4, columna 20
at esforcDeTasca (…:8:10) Qui l'ha cridada
at informeBacklog (…:14:14) Qui ha cridat aquesta
at Object.<anonymous> (…:19:13) El codi de nivell superior

Tres consells per aprofitar-la:

  1. La primera línia at és on ha petat; la causa sol ser més avall. Aquí la fallada real no és de pesDePrioritat, sinó de qui li ha passat null: és informeBacklog qui no ha validat les dades.
  2. <anonymous> és una funció sense nom. Una altra raó per assignar sempre les expressions a una const amb nom, com es va recomanar a 03-02.
  3. Els nombres són línia:columna. Al navegador són enllaços directes al codi a les DevTools.

La lliçó Depuració de JavaScript hi aprofundeix: punts d'interrupció, inspecció de la pila en viu, mapes de codi font i console.trace().

  1. Cas pràctic: ordenar js/app.js

Aquest és el fitxer js/app.js de Nómada Tasques escrit amb el pitjor ordre possible. Funciona a mitges, i això és el perillós.

// ✗ js/app.js — MAL ORDENAT
'use strict';

// 1. Es fa servir la configuració abans de definir-la
console.log(`Panell de Nómada Tasques · ${TITOL_APP}`);   // ✗ ReferenceError

const TITOL_APP = 'Taller Nómada';

// 2. Es crida una expressió abans d'assignar-la
console.log(formatarEstat('feta'));                       // ✗ ReferenceError

const formatarEstat = (estat) => {
  if (estat === 'feta') return 'Feta';
  return 'Pendent';
};

// 3. Això SÍ que funciona, i per això confon: és una declaració
console.log(pesDePrioritat('alta'));                      // 3

function pesDePrioritat(prioritat) {
  return prioritat === 'alta' ? 3 : prioritat === 'mitjana' ? 2 : 1;
}

// 4. var elevada: no dóna error, dóna undefined
console.log(`Tasques carregades: ${totalTasques}`);       // Tasques carregades: undefined

var totalTasques = 6;

// 5. Funció declarada dins d'un bloc
if (totalTasques > 0) {
  function iniciar() { console.log('Engegant…'); }
}
iniciar();                                                // ✗ TypeError en mode estricte

Cinc problemes en vint-i-cinc línies. I la versió corregida, que és la que faràs servir com a plantilla la resta del curs:

// ✓ js/app.js — ORDRE CORRECTE
'use strict';

// ─── 1. Configuració: constants primer ─────────────────────────────
const TITOL_APP = 'Taller Nómada';
const AVUI = '2026-09-20';
const LIMIT_SETMANAL = 40;                 // R7

// ─── 2. Dades ──────────────────────────────────────────────────────
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 responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const prioritats   = ['alta', 'mitjana', 'alta', 'baixa', 'mitjana', 'alta'];
const estats       = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];
const hores        = [12, 6, 14, 3, 8, 5];
const datesLimit   = ['2026-09-30', '2026-10-15', '2026-10-02',
                      '2026-09-12', '2026-11-05', '2026-09-05'];

// ─── 3. Funcions pures del domini ──────────────────────────────────
function pesDePrioritat(prioritat) {
  if (prioritat === 'alta') return 3;
  if (prioritat === 'mitjana') return 2;
  if (prioritat === 'baixa') return 1;
  return 0;
}

function estaVencuda(dataLimit, estat, avui) {
  return dataLimit < avui && estat !== 'feta';
}

function formatarEstat(estat) {
  if (estat === 'pendent') return 'Pendent';
  if (estat === 'en-curs') return 'En curs';
  if (estat === 'feta') return 'Feta';
  return 'Desconegut';
}

function descriureTasca(index, avui) {
  const avis = estaVencuda(datesLimit[index], estats[index], avui) ? ' ⚠ VENÇUDA' : '';
  return `[${index + 1}] ${titols[index]} · ${responsables[index]} · ` +
         `${formatarEstat(estats[index])} · ${hores[index]} h${avis}`;
}

// ─── 4. Punt d'entrada: SEMPRE al final ────────────────────────────
function iniciar() {
  console.log(`Panell de Nómada Tasques · ${TITOL_APP}`);

  let esforcTotal = 0;
  for (let i = 0; i < titols.length; i++) {
    console.log(descriureTasca(i, AVUI));
    esforcTotal += pesDePrioritat(prioritats[i]) * hores[i];
  }

  console.log(`Esforç ponderat: ${esforcTotal}`);
  console.log(`Límit setmanal per persona: ${LIMIT_SETMANAL} h`);
}

iniciar();

Sortida:

Panell de Nómada Tasques · Taller Nómada
[1] Redissenyar la sala polivalent · Iván · En curs · 12 h
[2] Cartelleria del taller de serigrafia · Marta · Pendent · 6 h
[3] Actualitzar el web de reserves · Lucía · Pendent · 14 h
[4] Inventari de tintes de serigrafia · Marta · Feta · 3 h
[5] Guia sobre enquadernació per a residents · Iván · En curs · 8 h
[6] Pressupost de la fusteria · Iván · Pendent · 5 h ⚠ VENÇUDA
Esforç ponderat: 124
Límit setmanal per persona: 40 h

L'ordre canònic d'un fitxer, que convé adoptar com a hàbit:

flowchart TD
    A["1 · 'use strict'"] --> B["2 · Constants de configuració"]
    B --> C["3 · Dades"]
    C --> D["4 · Funcions auxiliars i del domini"]
    D --> E["5 · Funció iniciar() / main()"]
    E --> F["6 · Una única crida: iniciar()"]

  1. Per què l'estil modern fa irrellevant el hoisting

Si compares les dues versions de l'apartat anterior, la conclusió salta a la vista: a la versió ordenada, el hoisting no importa gens. Cap línia no en depèn. I aquesta és exactament la meta.

Pràctica moderna Quin problema de hoisting elimina
const per defecte, let quan canviï, var mai Sense elevació a undefined ni contaminació global
Declarar abans de fer servir, sempre La TDZ no s'assoleix mai
Un únic punt d'entrada iniciar() cridat al final Tot està definit quan s'executa alguna cosa
No declarar funcions dins de blocs Sense diferències entre modes ni navegadors
'use strict' (o mòduls ES, que ja són estrictes) Errors primerencs en lloc de comportaments estranys
ESLint amb no-use-before-define L'editor avisa abans d'executar

Dit d'una altra manera: entendre el hoisting serveix sobretot per llegir codi aliè i per interpretar errors; no per escriure codi que en depengui. És coneixement de diagnòstic, no una tècnica.

Errors Habituals i Consells

1. Creure que var no s'eleva perquè el console.log dóna undefined. S'eleva; el que no s'eleva és l'assignació.

2. Creure que let i const no s'eleven. Sí que s'eleven; simplement romanen sense inicialitzar fins a la seva línia. Si no s'elevessin, l'error diria "is not defined" en lloc de "cannot access before initialization".

3. Fer servir typeof com a xarxa de seguretat amb let/const. No protegeix de la TDZ.

4. Declarar funcions dins d'if o for. Comportament diferent segons mode i motor. Fes servir expressions assignades a variables externes.

5. Ignorar l'ordre de l'stack trace. La línia de dalt és on ha petat, no necessàriament on és l'error. Llegeix tota la traça.

6. Confondre la pila de crides amb la memòria d'un closure. Els contextos de la pila es destrueixen en acabar; l'entorn capturat per un closure sobreviu perquè hi ha una funció que el referencia.

7. Consell: si dubtes de l'ordre del teu fitxer, mou-ho tot dins d'iniciar(). Un únic punt d'entrada cridat al final elimina de cop gairebé tots els problemes d'ordre.

8. Consell: activa no-use-before-define a la teva configuració d'ESLint. Converteix aquests errors en avisos de l'editor abans d'executar res (08-02).

Exercicis

Exercici 1 — Predir la sortida

Sense executar el codi, indica què imprimeix cada línia o quin error llança, i explica en quina fase es decideix.

'use strict';

console.log(a);            // (1)
console.log(f());          // (2)
console.log(g);            // (3)

var a = 1;
function f() { return 'f'; }
let b = 2;
const g = () => 'g';

function prova() {
  console.log(c);          // (4)
  console.log(d);          // (5)
  var c = 'c';
  let d = 'd';
}
prova();

Exercici 2 — Arreglar el fitxer

Aquest js/app.js té quatre problemes relacionats amb l'ordre i el hoisting. Identifica'ls, explica per què fallen i reescriu el fitxer complet seguint l'ordre canònic.

'use strict';

iniciar();

var comptador = 0;

function iniciar() {
  console.log(`Backlog de ${EMPRESA}`);
  for (var i = 0; i < 3; i++) {
    registrar(i);
  }
  console.log(`Registres: ${comptador}`);
  console.log(marge(45));
}

const EMPRESA = 'Taller Nómada';

if (true) {
  function registrar(n) { comptador++; console.log(`Registre ${n}`); }
}

const marge = (hores) => LIMIT - hores;
var LIMIT = 40;

Exercici 3 — Profunditat de la pila

Escriu una funció sumarHoresRecursiva(hores, index = 0) que sumi l'array d'hores del backlog cridant-se a si mateixa. Després:

  1. Comprova que retorna 48 amb el backlog.
  2. Genera un array de 50 000 hores i crida-la. Explica què passa i per què.
  3. Reescriu-la amb un bucle i explica per què aquesta versió no té el problema.

Solucions

Exercici 1

Núm. Resultat Fase on es decideix
(1) undefined Creació: var a es va registrar i inicialitzar a undefined
(2) 'f' Creació: la declaració de funció es va elevar completa
(3) ReferenceError: Cannot access 'g' before initialization Creació: const g registrada però a la TDZ
(4) undefined Creació del context de prova: var c a undefined
(5) ReferenceError let d és a la TDZ dins d'aquest context

A la pràctica el programa s'atura a (3) i les línies següents no arriben a executar-se. Per veure-les, cal eliminar o comentar aquesta línia, i aleshores s'aturaria a (5).

Exercici 2

Els quatre problemes:

# Problema Conseqüència
1 iniciar() es crida a la primera línia A dins es llegeix EMPRESA, que encara és a la TDZ → ReferenceError
2 registrar es declara dins d'un if en mode estricte Àmbit de bloc: a fora és undefinedTypeError
3 marge és una fletxa feta servir abans d'assignar-se ReferenceError
4 LIMIT és var, i a més es llegeix dins de marge Encara que l'ordre s'arreglés, var a escala global contamina; ha de ser const

Versió corregida:

'use strict';

// 1. Configuració
const EMPRESA = 'Taller Nómada';
const LIMIT = 40;

// 2. Estat del mòdul
let comptador = 0;

// 3. Funcions
function registrar(n) {
  comptador = comptador + 1;
  console.log(`Registre ${n}`);
}

const marge = (hores) => LIMIT - hores;

// 4. Punt d'entrada
function iniciar() {
  console.log(`Backlog de ${EMPRESA}`);
  for (let i = 0; i < 3; i++) {
    registrar(i);
  }
  console.log(`Registres: ${comptador}`);
  console.log(marge(45));
}

// 5. Única crida, al final
iniciar();

Sortida:

Backlog de Taller Nómada
Registre 0
Registre 1
Registre 2
Registres: 3
-5

Comentari: comptador és let perquè canvia, EMPRESA i LIMIT són const perquè no, i let i substitueix var i al bucle. El -5 revela a més un problema de negoci: 45 h superen el límit de 40 (R7), una cosa que una validació hauria d'impedir abans d'arribar aquí.

Exercici 3

function sumarHoresRecursiva(hores, index = 0) {
  if (index >= hores.length) return 0;                     // cas base
  return hores[index] + sumarHoresRecursiva(hores, index + 1);
}

// 1)
console.log(sumarHoresRecursiva([12, 6, 14, 3, 8, 5]));    // 48

// 2)
const moltes = [];
for (let i = 0; i < 50000; i++) moltes.push(1);

try {
  console.log(sumarHoresRecursiva(moltes));
} catch (error) {
  console.error(`${error.name}: ${error.message}`);
}
// RangeError: Maximum call stack size exceeded

// 3)
function sumarHoresIterativa(hores) {
  let total = 0;
  for (const h of hores) total += h;
  return total;
}

console.log(sumarHoresIterativa(moltes));   // 50000

Explicació: la versió recursiva apila 50 001 contextos, perquè cap crida no pot acabar fins que acabi la següent (la suma es fa en tornar). El motor exhaureix l'espai de la pila i llança RangeError. La versió iterativa fa servir un sol context i una variable acumuladora, així que el seu consum de pila és constant independentment de la mida de l'array. Aquest és exactament el compromís que analitzaràs a Recursivitat.

Conclusió

Ja saps què fa el motor abans d'executar el teu codi. Cada àmbit passa per una fase de creació, on es registren les declaracions —les funcions completes i a punt, les var com a undefined, i let/const sense inicialitzar—, i una fase d'execució, on s'assignen els valors línia a línia. Aquest és tot el misteri del hoisting: no es mou res, simplement el motor ja coneixia els noms. La zona morta temporal de let i const no és un obstacle sinó una protecció: converteix en error immediat el que amb var era un undefined silenciós, i per això el missatge distingeix entre "is not defined" i "cannot access before initialization".

Has vist també la maquinària que sosté tot l'anterior: el context d'execució, amb el seu entorn de variables, la seva referència a l'àmbit exterior —la cadena que explicava els closures de 03-04— i el seu this; el context global, un per script, on var contamina l'objecte global i let/const no; i la pila de crides, d'un sol fil i mida limitada, la saturació de la qual produeix el RangeError: Maximum call stack size exceeded. I saps llegir un stack trace: què ha fallat a dalt, qui ha cridat qui a sota, i que la causa arrel gairebé mai no és a la primera línia.

El més important és la conclusió pràctica: amb const per defecte, les funcions declarades abans de fer-les servir, cap var, cap funció dins de blocs i un únic iniciar() cridat al final, el hoisting deixa d'importar. El js/app.js ordenat d'aquesta lliçó és la plantilla que seguiràs la resta del curs.

Amb això tanques la part de "com funcionen les funcions per dins". El que ve ara és treure'n partit. Ja has fregat la idea diverses vegades —llistarSegons(filtre) rebia una funció, crearFiltrePerResponsable en retornava una— sense posar-li nom. Aquest nom és funcions d'ordre superior, i amb elles construiràs a Funcions d'Ordre Superior les teves pròpies versions de map, filter i reduce, un sistema de composició i un motor d'informes configurable per a Taller Nómada.

Curs de JavaScript: De Principiant a Avançat

Mòdul 1: Introducció a JavaScript

Mòdul 2: Estructures de Control

Mòdul 3: Funcions

Mòdul 4: Objectes i Arrays

Mòdul 5: Objectes i Funcions Avançades

Mòdul 6: El Model d'Objectes del Document (DOM)

Mòdul 7: APIs del Navegador i Temes Avançats

Mòdul 8: Proves i Depuració

Mòdul 9: Rendiment i Optimització

Mòdul 10: Frameworks i Llibreries de JavaScript

Mòdul 11: Projecte Final

© Copyright 2026. Tots els drets reservats