La lliçó anterior va acabar amb una incomoditat concreta: descriureTasca rebia vuit paràmetres, els validadors en rebien quatre encara que cadascun en fes servir només un, i no sabies què passa quan algú crida una funció amb menys arguments dels declarats. Aquesta lliçó s'ocupa exactament d'això: com entren les dades en una funció i com en surten els resultats. És la part del disseny de funcions que més impacte té en la qualitat del codi, perquè una signatura ben pensada fa que la funció es faci servir bé sense necessitat de llegir-ne l'interior. En acabar sabràs donar valors per defecte, acceptar un nombre variable d'arguments, agrupar paràmetres en un objecte d'opcions, retornar diversos valors alhora i —el més important— distingir una funció pura d'una que provoca efectes secundaris.

Contingut

  1. Arguments de més i de menys
  2. Pas per valor: els primitius
  3. Pas per valor de la referència: objectes i arrays
  4. L'avís sobre mutar els arguments
  5. Paràmetres per defecte
  6. Dependències entre paràmetres per defecte
  7. L'objecte arguments (llegat)
  8. Paràmetres rest ...args
  9. El patró "objecte d'opcions"
  10. return: retornar aviat i retornar un sol tipus
  11. Retornar diversos valors
  12. Funcions pures i efectes secundaris
  13. Cas pràctic: crearTasca i resumirCarrega
  14. Errors Habituals i Consells
  15. Exercicis
  16. Conclusió

  1. Arguments de més i de menys

En JavaScript, el nombre d'arguments que passes no cal que coincideixi amb el nombre de paràmetres declarats. No hi ha error, ni avís.

function descriureCarrega(responsable, hores, limit) {
  console.log(`${responsable} · ${hores} h · límit ${limit}`);
}

descriureCarrega('Iván', 25, 40);        // Iván · 25 h · límit 40
descriureCarrega('Iván', 25);            // Iván · 25 h · límit undefined
descriureCarrega('Iván');                // Iván · undefined h · límit undefined
descriureCarrega('Iván', 25, 40, 'x');   // Iván · 25 h · límit 40   ← el quart s'ignora

Les regles són senzilles:

Situació Què passa
Falten arguments Els paràmetres sobrants valen undefined
Sobren arguments S'ignoren (però continuen accessibles, vegeu l'apartat 7)
Arguments en un altre ordre S'assignen per posició, no per nom: silenci i resultats absurds

Aquesta permissivitat és còmoda i perillosa a parts iguals. El dany real apareix quan l'undefined s'escola en un càlcul:

function margeSetmanal(horesAssignades, limit) {
  return limit - horesAssignades;
}

console.log(margeSetmanal(25));   // NaN   ← undefined - 25

Un NaN que es propaga per tots els càlculs següents i no dóna error fins molt després. Les dues defenses contra això són els paràmetres per defecte (apartat 5) i la validació explícita que vas aprendre a Gestió d'Errors.

  1. Pas per valor: els primitius

Quan passes un primitiu (nombre, cadena, booleà, null, undefined, símbol, bigint), la funció rep una còpia del valor. Modificar-lo a dins no afecta la variable de fora.

let horesIvan = 25;

function afegirHores(hores, extra) {
  hores = hores + extra;        // modifica la CÒPIA local
  return hores;
}

const nouTotal = afegirHores(horesIvan, 5);

console.log(horesIvan);   // 25  ← intacta
console.log(nouTotal);    // 30
flowchart LR
    A["horesIvan<br/>25"] -->|copia el valor| B["paràmetre hores<br/>25 → 30"]
    B -.->|no torna| A

Això és una garantia molt valuosa: una funció no pot canviar mai els teus nombres i els teus textos per sorpresa. El que hi facis a dins es queda a dins.

  1. Pas per valor de la referència: objectes i arrays

Amb objectes i arrays la cosa canvia, i és la font número u d'errors subtils. Recorda el que vas veure a Variables i Tipus de Dades: una variable que conté un objecte no desa l'objecte, desa una referència (una adreça) a l'objecte.

En cridar una funció, es copia aquesta referència. La còpia i l'original apunten al mateix objecte.

const tasca = {
  id: 1,
  titol: 'Redissenyar la sala polivalent',
  estat: 'en-curs',
  horesEstimades: 12
};

function marcarComFeta(t) {
  t.estat = 'feta';             // ← modifica l'objecte COMPARTIT
}

marcarComFeta(tasca);
console.log(tasca.estat);       // 'feta'  ← ha canviat a fora!

Però reassignar el paràmetre no afecta res de fora, perquè només canvies cap on apunta la còpia local:

function reemplacar(t) {
  t = { id: 99, titol: 'Una altra cosa' };   // la còpia local apunta a un altre objecte
}

reemplacar(tasca);
console.log(tasca.id);   // 1  ← intacta
flowchart TD
    subgraph M["Memòria"]
        O["{ id: 1, titol: '…', estat: 'en-curs' }"]
        O2["{ id: 99, titol: 'Una altra cosa' }"]
    end
    V["const tasca"] --> O
    P1["paràmetre t<br/>(mutació: t.estat = …)"] --> O
    P2["paràmetre t<br/>(reassignació: t = …)"] --> O2

Resum en una taula, perquè aquesta distinció cal tenir-la automatitzada:

Operació dins de la funció Afecta fora?
t.estat = 'feta' (mutar una propietat)
llista.push(nova) (mutar un array)
t = { ... } (reassignar el paràmetre) No
llista = [] (reassignar el paràmetre) No
n = n + 1 amb un nombre No

La frase tècnica correcta és que JavaScript passa sempre per valor; el que passa és que, en el cas dels objectes, el valor que es copia és una referència.

  1. L'avís sobre mutar els arguments

Mutar un objecte rebut per paràmetre és legal, de vegades útil i gairebé sempre una mala idea si no està anunciat al nom de la funció. El motiu és que trenca l'expectativa de qui la crida:

// ✗ Sorpresa: el nom diu "calcular", però a més modifica
function calcularUrgencia(tasca, avui) {
  tasca.urgencia = pesDePrioritat(tasca.prioritat) * 10;   // efecte ocult
  return tasca.urgencia;
}

Qui llegeix const u = calcularUrgencia(t, AVUI); no espera que t hagi canviat. I si més endavant es desa t a l'emmagatzematge del navegador, hi apareixerà un camp urgencia que ningú no ha demanat.

Tres maneres de fer-ho bé:

// ✓ 1. No mutar: només calcular i retornar
function calcularUrgencia(prioritat, diesRestants) {
  return pesDePrioritat(prioritat) * 10 - diesRestants;
}

// ✓ 2. Retornar una còpia modificada
function ambEstat(tasca, nouEstat) {
  const copia = Object.assign({}, tasca);   // còpia superficial
  copia.estat = nouEstat;
  return copia;
}

// ✓ 3. Mutar, però anunciant-ho al nom
function aplicarEstatEnTasca(tasca, nouEstat) {
  tasca.estat = nouEstat;
}

L'opció 2 és la que farem servir per defecte a Nómada Tasques. Les còpies d'objectes —inclosa la diferència entre còpia superficial i profunda, que aquí només esmentem— s'estudien a JSON i Còpies d'Objectes.

  1. Paràmetres per defecte

Des d'ES2015 pots donar un valor a un paràmetre per quan no es passi (o es passi undefined):

function crearResum(titol, responsable = 'sense assignar', hores = 0) {
  return `${titol} · ${responsable} · ${hores} h`;
}

console.log(crearResum('Cartelleria del taller de serigrafia'));
// Cartelleria del taller de serigrafia · sense assignar · 0 h

console.log(crearResum('Cartelleria del taller de serigrafia', 'Marta', 6));
// Cartelleria del taller de serigrafia · Marta · 6 h

Detall crític: el valor per defecte s'aplica només amb undefined, no amb qualsevol valor falsy.

console.log(crearResum('Inventari', undefined, 0));   // Inventari · sense assignar · 0 h
console.log(crearResum('Inventari', null, 0));        // Inventari · null · 0 h
console.log(crearResum('Inventari', '', 0));          // Inventari ·  · 0 h

Compara-ho amb el patró antic, que sí que reaccionava a tots els falsy i per això donava problemes:

Tècnica S'activa amb Problema
hores = hores || 8 0, '', NaN, false, null, undefined Un 0 legítim es converteix en 8
hores = hores ?? 8 null, undefined Correcte en la majoria de casos
function f(hores = 8) Només undefined El més precís; el que has de fer servir

Els valors per defecte s'avaluen a cada crida, no un cop en definir la funció. Això permet fer servir expressions:

function registrarCanvi(tascaId, data = new Date().toISOString().slice(0, 10)) {
  console.log(`[${data}] Canvi a la tasca ${tascaId}`);
}

Cada crida recalcula la data. Si el valor per defecte s'hagués avaluat una sola vegada, totes les crides compartirien la mateixa data (que és precisament l'error clàssic en altres llenguatges).

  1. Dependències entre paràmetres per defecte

Un paràmetre per defecte pot fer servir els paràmetres declarats a la seva esquerra:

function planificar(horesEstimades, horesPerDia = 8, dies = Math.ceil(horesEstimades / horesPerDia)) {
  return `${horesEstimades} h → ${dies} dia/es a ${horesPerDia} h/dia`;
}

console.log(planificar(14));           // 14 h → 2 dia/es a 8 h/dia
console.log(planificar(14, 4));        // 14 h → 4 dia/es a 4 h/dia
console.log(planificar(14, 4, 1));     // 14 h → 1 dia/es a 4 h/dia

A l'inrevés no funciona, perquè els paràmetres s'inicialitzen d'esquerra a dreta:

function malament(dies = hores / 8, hores) { /* ... */ }
malament(undefined, 16);   // ✗ ReferenceError: Cannot access 'hores' before initialization

Això és la mateixa zona morta temporal (TDZ) de let/const aplicada a la llista de paràmetres; el mecanisme complet és a Hoisting i el Context d'Execució.

Regla pràctica: col·loca primer els paràmetres obligatoris i després els que tenen valor per defecte. Si has d'escriure f(dada, undefined, altre) per saltar-te'n un, la signatura està mal dissenyada i necessites el patró de l'apartat 9.

Un truc útil per exigir un paràmetre sense escriure un if:

const obligatori = (nom) => {
  throw new Error(`Falta el paràmetre obligatori: ${nom}`);
};

function assignarTasca(tascaId = obligatori('tascaId'), responsable = obligatori('responsable')) {
  return `Tasca ${tascaId} assignada a ${responsable}`;
}

console.log(assignarTasca(3, 'Lucía'));   // Tasca 3 assignada a Lucía
assignarTasca(3);                         // ✗ Error: Falta el paràmetre obligatori: responsable

Funciona perquè el valor per defecte és una expressió que només s'avalua si l'argument falta, i aquesta expressió llança. És fallar aviat, amb un missatge que diu exactament què falta.

  1. L'objecte arguments (llegat)

Dins de qualsevol funció declarada amb function existeix una variable implícita anomenada arguments amb tots els arguments rebuts, estiguin declarats o no:

function sumarHores() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(sumarHores(12, 6, 14, 3, 8, 5));   // 48

Aquest 48 és el total d'hores del backlog de Taller Nómada. Però arguments té tres inconvenients seriosos:

Problema Conseqüència
No és un array de debò És array-like: té length i índexs, però cap mètode d'array
No existeix a les fletxes () => arguments.length dóna ReferenceError o agafa el de fora
No apareix a la signatura En llegir function sumarHores() sembla que no rep res

Aquest tercer punt és el pitjor: la signatura menteix. Avui arguments només es fa servir en mantenir codi antic. El seu substitut és millor en tot.

  1. Paràmetres rest ...args

Els paràmetres rest recullen "tota la resta" en un array de debò:

function sumarHores(...hores) {
  let total = 0;
  for (const h of hores) {
    total += h;
  }
  return total;
}

console.log(sumarHores(12, 6, 14, 3, 8, 5));   // 48
console.log(sumarHores());                     // 0

I es poden combinar amb paràmetres normals, sempre en darrer lloc:

function informeDeCarrega(responsable, ...hores) {
  let total = 0;
  for (const h of hores) total += h;
  return `${responsable}: ${hores.length} tasques, ${total} h`;
}

console.log(informeDeCarrega('Iván', 12, 8, 5));   // Iván: 3 tasques, 25 h
console.log(informeDeCarrega('Lucía', 14));        // Lucía: 1 tasques, 14 h
console.log(informeDeCarrega('Ningú'));            // Ningú: 0 tasques, 0 h

Comparació directa:

arguments ...rest
Tipus Objecte array-like Array real
Funciona a les fletxes No
Visible a la signatura No
Selectiu No, són tots Sí, només els no declarats
Mètodes d'array No Sí (Mòdul 4)
Recomanat Només en codi heretat Sempre

Els tres punts ... apareixeran també com a operador spread, que fa el contrari: desplegar un array en arguments solts. Tots dos usos es veuen junts a Desestructuració d'Objectes, Spread i Rest.

  1. El patró "objecte d'opcions"

Torna al problema amb què va començar la lliçó. Aquesta és la signatura de descriureTasca tal com va quedar a 03-01:

descriureTasca(1, 'Redissenyar la sala polivalent', 'Iván', 'alta', 'en-curs', 12, '2026-09-30', AVUI);

Vuit arguments posicionals. Els problemes són evidents:

  • Ningú no recorda l'ordre. Anava abans la prioritat o l'estat?
  • Un canvi d'ordre a la definició trenca totes les crides sense donar error.
  • Per ometre el sisè cal passar undefined explícit.
  • La crida, llegida tota sola, no diu què significa cada valor.

L'objecte d'opcions consisteix a passar un únic objecte les claus del qual anomenen cada dada:

function descriureTasca(opcions) {
  const marca = opcions.estat === 'feta' ? '✓' : '○';
  const avis = opcions.dataLimit < opcions.avui && opcions.estat !== 'feta'
    ? ' ⚠ VENÇUDA'
    : '';
  return `${marca} [${opcions.id}] ${opcions.titol} · ${opcions.responsable} · ${opcions.horesEstimades} h${avis}`;
}

console.log(descriureTasca({
  id: 6,
  titol: 'Pressupost de la fusteria',
  responsable: 'Iván',
  prioritat: 'alta',
  estat: 'pendent',
  horesEstimades: 5,
  dataLimit: '2026-09-05',
  avui: '2026-09-20'
}));
// ○ [6] Pressupost de la fusteria · Iván · 5 h ⚠ VENÇUDA

Avantatges i cost, perquè la decisió sigui conscient:

Paràmetres posicionals Objecte d'opcions
Llegibilitat a la crida Baixa amb més de 3 Alta: cada valor va etiquetat
Ordre Obligatori Irrellevant
Ometre'n un del mig undefined explícit Simplement no s'hi posa
Afegir una dada nova Trenca o allarga la signatura Afegir una clau més
Verbositat Menor Major (cal escriure les claus)
Errades d'escriptura a les claus No aplica undefined silenciós

Quan fer servir cadascun: amb un o dos paràmetres, posicionals sempre. Amb tres, depèn. Amb quatre o més, o si hi ha diversos opcionals del mateix tipus, objecte d'opcions.

I una variant molt habitual: paràmetres obligatoris posicionals + un objecte final d'opcions.

function formatarBacklog(tasques, opcions = {}) {
  const separador = opcions.separador ?? '\n';
  const mostrarHores = opcions.mostrarHores ?? true;
  const ordre = opcions.ordre ?? 'data';
  // ...
}

formatarBacklog(backlog);                                  // tot per defecte
formatarBacklog(backlog, { ordre: 'prioritat' });          // només canvia el que interessa
formatarBacklog(backlog, { mostrarHores: false, ordre: 'responsable' });

Fixa't en opcions = {}: sense aquest valor per defecte, cridar formatarBacklog(backlog) donaria un TypeError en intentar llegir opcions.separador d'undefined. A Desestructuració d'Objectes veuràs com escriure això de manera molt més compacta.

  1. return: retornar aviat i retornar un sol tipus

10.1 Retornar aviat

Ja ho vas aplicar a 03-01 amb les guardes. La regla és: tracta els casos excepcionals al principi i surt; deixa el cas normal al final, sense imbricar.

// ✗ Imbricació en escala
function calcularMarge(responsable, horesAssignades, limit) {
  if (responsable !== null) {
    if (typeof horesAssignades === 'number') {
      if (horesAssignades <= limit) {
        return limit - horesAssignades;
      } else {
        return 0;
      }
    }
  }
}

// ✓ Guardes i sortida primerenca
function calcularMarge(responsable, horesAssignades, limit) {
  if (responsable === null) return limit;
  if (typeof horesAssignades !== 'number') return limit;
  if (horesAssignades >= limit) return 0;
  return limit - horesAssignades;
}

La segona versió té la mateixa lògica, un nivell d'indentació i —detall important— cap branca sense return. La primera retorna undefined si responsable és null, una cosa que ningú no ha escrit a propòsit.

10.2 Retornar sempre el mateix tipus

Una funció que de vegades retorna un nombre, de vegades una cadena i de vegades false obliga qui la fa servir a comprovar el tipus cada cop:

// ✗ Tres tipus diferents segons el cas
function cercarHores(responsable) {
  if (responsable === null) return 'sense responsable';
  if (responsable === 'Iván') return 25;
  return false;
}

const h = cercarHores('Marta');
console.log(h + 10);   // 10  ← false + 10, sense error i sense sentit

Alternatives correctes, per ordre de preferència:

Estratègia Exemple Quan
Un valor neutre del mateix tipus return 0; Quan "res" té un equivalent numèric natural
null explícit return null; Quan l'absència és un resultat legítim
Llançar un error throw new Error(...) Quan la situació indica una fallada real
Un objecte resultat { ok: false, motiu: '…' } Quan qui la crida necessita saber per què
function cercarHores(responsable, responsables, hores) {
  if (responsable === null) return null;      // absència declarada
  let total = 0;
  let trobat = false;
  for (let i = 0; i < responsables.length; i++) {
    if (responsables[i] === responsable) {
      total += hores[i];
      trobat = true;
    }
  }
  return trobat ? total : null;
}

Ara qui la crida sap que rep un nombre o null, i pot fer servir ?? 0 per al cas absent.

  1. Retornar diversos valors

Una funció només pot retornar un valor. Però aquest valor pot ser un contenidor. Hi ha dues opcions:

// Opció A: objecte (recomanat quan les dades tenen nom)
function resumirCarrega(hores, estats) {
  let total = 0;
  let obertes = 0;
  let fetes = 0;
  for (let i = 0; i < hores.length; i++) {
    total += hores[i];
    if (estats[i] === 'feta') fetes += hores[i];
    else obertes += hores[i];
  }
  return { total: total, obertes: obertes, fetes: fetes };
}

const r = resumirCarrega([12, 6, 14, 3, 8, 5],
                         ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent']);
console.log(r.total, r.obertes, r.fetes);   // 48 45 3
// Opció B: array (només quan l'ordre és evident i són pocs)
function rangDeDates(dates) {
  let minima = dates[0];
  let maxima = dates[0];
  for (const d of dates) {
    if (d < minima) minima = d;
    if (d > maxima) maxima = d;
  }
  return [minima, maxima];
}

const rang = rangDeDates(['2026-09-30', '2026-10-15', '2026-09-05', '2026-11-05']);
console.log(rang[0], rang[1]);   // 2026-09-05 2026-11-05
Objecte Array
Les dades van etiquetades No, per posició
Ordre significatiu No
Afegir una dada després Fàcil, no trenca res Trenca les posicions
Quan Gairebé sempre 2-3 valors amb ordre natural (mín/màx, x/y)

Llegir r.total, r.obertes, rang[0], rang[1] funciona però és verbós. Existeix una sintaxi molt més còmoda per desempaquetar aquests resultats —desestructuració— que aprendràs a Desestructuració d'Arrays i Desestructuració d'Objectes:

// Avançament: així s'escriurà a partir del Mòdul 4
const { total, obertes, fetes } = resumirCarrega(hores, estats);
const [minima, maxima] = rangDeDates(dates);

  1. Funcions pures i efectes secundaris

Una funció és pura si compleix dues condicions:

  1. Amb els mateixos arguments retorna sempre el mateix resultat.
  2. No provoca cap efecte secundari: no modifica res de fora, no imprimeix, no desa, no llegeix l'hora ni un valor aleatori.
// ✓ PURA: només depèn dels seus arguments i només retorna
function pesDePrioritat(prioritat) {
  if (prioritat === 'alta') return 3;
  if (prioritat === 'mitjana') return 2;
  if (prioritat === 'baixa') return 1;
  return 0;
}

// ✗ IMPURA: depèn d'una variable externa
let limitSetmanal = 40;
function margeDe(hores) {
  return limitSetmanal - hores;      // el resultat canvia si canvia limitSetmanal
}

// ✗ IMPURA: efecte secundari (escriu a la consola)
function informar(responsable, hores) {
  console.log(`${responsable}: ${hores} h`);
}

// ✗ IMPURA: depèn del rellotge
function diesFinsA(dataLimit) {
  const avui = new Date();           // resultat diferent cada dia
  return Math.ceil((new Date(dataLimit) - avui) / 86400000);
}

Les tres impures s'arreglen igual: el que la funció necessita de l'exterior, hi entra per paràmetre.

function margeDe(hores, limitSetmanal) {
  return limitSetmanal - hores;
}

function diesFinsA(dataLimit, avui) {
  return Math.ceil((new Date(dataLimit) - new Date(avui)) / 86400000);
}

console.log(diesFinsA('2026-09-30', '2026-09-20'));   // 10
console.log(diesFinsA('2026-09-05', '2026-09-20'));   // -15  (vençuda)

Per això el projecte fa servir AVUI = '2026-09-20' com a constant i la passa com a argument en lloc de llegir el rellotge dins de cada funció.

Avantatges concrets de les funcions pures:

Avantatge Per què
Fàcils de provar Un test és "donat X, espero Y"; sense preparar entorn ni simular res (08-03)
Fàcils de raonar Per entendre-les n'hi ha prou de llegir-ne el cos; no hi ha estat amagat
Reutilitzables Funcionen igual a la consola, al navegador i a Node
Memoïtzables El seu resultat es pot posar a la memòria cau sense risc (03-04)
Sense errors d'ordre Tant se val quan es cridin

Això no significa que els efectes secundaris siguin dolents: sense ells un programa no imprimiria res ni desaria res. L'estratègia és concentrar-los: un nucli gran de funcions pures que calculen, i una capa fina de funcions impures que imprimeixen, desen o pinten. Aquesta separació és la que farà que al Mòdul 8 puguis provar tota la lògica de Nómada Tasques sense obrir el navegador.

flowchart TD
    A["Entrada: dades del formulari"] --> B["Nucli pur<br/>validarTasca · crearTasca<br/>resumirCarrega · calcularUrgencia"]
    B --> C["Capa d'efectes<br/>console.log · localStorage<br/>DOM · fetch"]
    C --> D["Sortida: pantalla, disc, xarxa"]
    style B fill:#e8f5e9,stroke:#2e7d32
    style C fill:#fff3e0,stroke:#ef6c00

  1. Cas pràctic: crearTasca i resumirCarrega

Ajuntem tot el de la lliçó en dues funcions que acompanyaran el projecte la resta del curs.

'use strict';

const AVUI = '2026-09-20';

const obligatori = (camp) => {
  throw new Error(`Falta el camp obligatori: ${camp}`);
};

let ultimId = 0;

/**
 * Crea una tasca vàlida a partir d'un objecte d'opcions.
 * Aplica R1 (id correlatiu), R5 (neix pendent), R8 (responsable null),
 * R9 (etiquetes en minúscules i sense duplicats).
 * Impura a propòsit: consumeix el comptador d'ids.
 */
function crearTasca(opcions = {}) {
  const titol = opcions.titol ?? obligatori('titol');
  const horesEstimades = opcions.horesEstimades ?? obligatori('horesEstimades');
  const dataLimit = opcions.dataLimit ?? obligatori('dataLimit');

  const responsable = opcions.responsable ?? null;      // R8
  const prioritat = opcions.prioritat ?? 'mitjana';
  const etiquetes = normalitzarEtiquetes(opcions.etiquetes ?? []);   // R9
  const revisor = opcions.revisor ?? null;

  if (titol.trim().length === 0) throw new Error('R2: el títol no pot estar buit.');
  if (horesEstimades <= 0 || horesEstimades > 40) throw new Error('R3: hores fora de rang (0-40).');

  ultimId = ultimId + 1;                                // R1

  return {
    id: ultimId,
    titol: titol.trim(),
    responsable: responsable,
    prioritat: prioritat,
    estat: 'pendent',                                   // R5
    etiquetes: etiquetes,
    horesEstimades: horesEstimades,
    dataLimit: dataLimit,
    revisor: revisor
  };
}

/** Pura: minúscules, sense espais sobrants i sense duplicats (R9). */
function normalitzarEtiquetes(etiquetes) {
  const netes = [];
  for (const etiqueta of etiquetes) {
    const normalitzada = String(etiqueta).trim().toLowerCase();
    if (normalitzada === '') continue;
    let repetida = false;
    for (let j = 0; j < netes.length; j++) {
      if (netes[j] === normalitzada) { repetida = true; break; }
    }
    if (!repetida) netes.push(normalitzada);
  }
  return netes;
}

const nova = crearTasca({
  titol: '  Renovar el torn de la fusteria  ',
  horesEstimades: 10,
  dataLimit: '2026-10-20',
  responsable: 'Lucía',
  prioritat: 'alta',
  etiquetes: ['Fusteria', 'fusteria', ' MANTENIMENT ']
});

console.log(nova);
// {
//   id: 1,
//   titol: 'Renovar el torn de la fusteria',
//   responsable: 'Lucía',
//   prioritat: 'alta',
//   estat: 'pendent',
//   etiquetes: [ 'fusteria', 'manteniment' ],
//   horesEstimades: 10,
//   dataLimit: '2026-10-20',
//   revisor: null
// }

Repassa quines tècniques de la lliçó hi apareixen: objecte d'opcions amb opcions = {} per defecte, ?? per als valors opcionals, la funció obligatori() com a valor per defecte que llança, validació amb fallada primerenca, retorn d'un objecte nou (sense mutar opcions) i una funció auxiliar pura (normalitzarEtiquetes).

Ara el resum de càrrega per responsable, completament pur:

/**
 * Retorna diversos totals d'un responsable en un sol objecte.
 * Pura: mateixos arrays i mateix responsable → mateix resultat.
 */
function resumirCarrega(responsables, hores, estats, datesLimit, responsable, avui = AVUI) {
  let totalHores = 0;
  let horesObertes = 0;
  let tasques = 0;
  let vencudes = 0;

  for (let i = 0; i < responsables.length; i++) {
    if (responsables[i] !== responsable) continue;

    tasques++;
    totalHores += hores[i];

    if (estats[i] !== 'feta') {
      horesObertes += hores[i];
      if (datesLimit[i] < avui) vencudes++;
    }
  }

  return {
    responsable: responsable,
    tasques: tasques,
    totalHores: totalHores,
    horesObertes: horesObertes,
    vencudes: vencudes,
    marge: 40 - horesObertes,            // R7
    sobrecarregat: horesObertes > 40     // R7
  };
}

const responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const hores        = [12, 6, 14, 3, 8, 5];
const estats       = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];
const datesLimit   = ['2026-09-30', '2026-10-15', '2026-10-02',
                      '2026-09-12', '2026-11-05', '2026-09-05'];

const equip = ['Iván', 'Marta', 'Lucía'];
for (const persona of equip) {
  const r = resumirCarrega(responsables, hores, estats, datesLimit, persona);
  console.log(`${r.responsable}: ${r.tasques} tasques · ${r.horesObertes}/${r.totalHores} h obertes · ` +
              `marge ${r.marge} h · vençudes ${r.vencudes}`);
}
// Iván: 3 tasques · 25/25 h obertes · marge 15 h · vençudes 1
// Marta: 2 tasques · 6/9 h obertes · marge 34 h · vençudes 0
// Lucía: 1 tasques · 14/14 h obertes · marge 26 h · vençudes 0

Set dades en un sol retorn, i zero efectes secundaris: qui la crida decideix si els imprimeix, els pinta al Mòdul 6 o els compara. Aquesta és exactament la separació nucli pur / capa d'efectes del diagrama anterior.

Errors Habituals i Consells

1. Mutar un objecte rebut sense dir-ho. Si la teva funció es diu calcular… o obtenir…, no ha de canviar res. Retorna un valor nou.

2. Fer servir || per a valors per defecte numèrics.

function ambMarge(hores) {
  hores = hores || 8;      // ✗ un 0 legítim es converteix en 8
  return hores;
}
console.log(ambMarge(0));   // 8  ← malament

Fes servir ?? o, millor, un paràmetre per defecte real.

3. Posar paràmetres opcionals abans dels obligatoris. Obliga a escriure undefined a les crides i és una olor de disseny millorable.

4. Retornar undefined sense voler. Un if sense else i sense return final deixa branques mudes. Comprova que tots els camins retornen alguna cosa.

5. Errada en una clau de l'objecte d'opcions.

crearTasca({ titlo: 'Nova', horesEstimades: 4, dataLimit: '2026-10-01' });
// ✗ Error: Falta el camp obligatori: titol

En aquest cas l'obligatori() ho detecta; en opcions no obligatòries, l'errada passa desapercebuda i el valor per defecte s'aplica en silenci. És el preu del patró, i la raó per la qual convé validar les claus acceptades en funcions crítiques.

6. Confondre rest amb spread. ... a la definició recull (rest); ... a la crida desplega (spread). Tots dos a 04-07.

7. Consell: escriu la signatura abans que el cos. Si la signatura no cap còmodament en una línia, replanteja-la abans de programar res.

8. Consell: tres paràmetres és el llindar d'alerta. A partir d'aquí, pregunta't si aquestes dades en realitat formen una sola cosa (una tasca, unes opcions, un rang).

Exercicis

Exercici 1 — Pur o impur

Classifica cada funció com a pura o impura i, si és impura, reescriu-la perquè sigui pura sense canviar el que calcula.

// a)
const IVA = 0.21;
function ambImpostos(base) { return base * (1 + IVA); }

// b)
let comptadorCrides = 0;
function urgencia(prioritat, dies) {
  comptadorCrides++;
  return pesDePrioritat(prioritat) * 10 - dies;
}

// c)
function ordenarEtiquetes(etiquetes) {
  etiquetes.sort();
  return etiquetes;
}

// d)
function diesDeMarge(dataLimit) {
  const avui = new Date().toISOString().slice(0, 10);
  return dataLimit >= avui ? 'a temps' : 'vençuda';
}

Exercici 2 — Redissenyar una signatura

Aquesta funció té una signatura poc manejable. Redissenya-la amb objecte d'opcions i valors per defecte, de manera que les tres crides del final funcionin.

function generarInforme(tasques, responsable, incloureFetes, ordenarPer, format, ample, mostrarVencudes) {
  // ...
}

// Crides que han de funcionar després del redisseny:
// 1) Només el backlog, tota la resta per defecte
// 2) El backlog de l'Iván, incloent-hi les fetes
// 3) El backlog ordenat per prioritat, en format compacte

Exercici 3 — resumirBacklog amb rest i retorn múltiple

Escriu resumirBacklog(estats, hores, ...responsablesFiltre) que:

  • Si no es passa cap responsable, resumeixi tot el backlog.
  • Si es passen un o més noms, resumeixi només les tasques d'aquestes persones.
  • Retorni un objecte amb tasques, horesTotals, horesObertes i percentatgeCompletat (hores fetes sobre el total, arrodonit a un decimal).

Prova-la amb el backlog complet, amb 'Iván' i amb 'Marta', 'Lucía'.

Solucions

Exercici 1

Cas Veredicte Motiu
a) Pura a la pràctica Depèn d'IVA, però és una constant immutable del mòdul. Acceptable; si IVA fos let, seria impura
b) Impura Muta comptadorCrides, una variable externa
c) Impura sort() ordena l'array al lloc: muta l'argument
d) Impura Llegeix el rellotge: el resultat canvia amb el dia
// b) Sense efecte secundari. Si cal comptar, que ho faci qui la crida.
function urgencia(prioritat, dies) {
  return pesDePrioritat(prioritat) * 10 - dies;
}

// c) Copiar abans d'ordenar
function ordenarEtiquetes(etiquetes) {
  const copia = [];
  for (const e of etiquetes) copia.push(e);
  copia.sort();
  return copia;
}

const originals = ['serigrafia', 'disseny', 'fusteria'];
const ordenades = ordenarEtiquetes(originals);
console.log(originals);   // [ 'serigrafia', 'disseny', 'fusteria' ]  ← intacte
console.log(ordenades);   // [ 'disseny', 'fusteria', 'serigrafia' ]

// d) La data hi entra per paràmetre
function diesDeMarge(dataLimit, avui) {
  return dataLimit >= avui ? 'a temps' : 'vençuda';
}
console.log(diesDeMarge('2026-09-05', '2026-09-20'));   // vençuda

Exercici 2

function generarInforme(tasques, opcions = {}) {
  const responsable     = opcions.responsable     ?? null;    // null = tot l'equip
  const incloureFetes   = opcions.incloureFetes   ?? false;
  const ordenarPer      = opcions.ordenarPer      ?? 'data';
  const format          = opcions.format          ?? 'detallat';
  const ample           = opcions.ample           ?? 80;
  const mostrarVencudes = opcions.mostrarVencudes ?? true;

  return `Informe [${responsable ?? 'equip'}] · ordre: ${ordenarPer} · format: ${format} · ` +
         `${incloureFetes ? 'amb' : 'sense'} fetes · ample ${ample}` +
         `${mostrarVencudes ? ' · marcant vençudes' : ''}`;
}

console.log(generarInforme(backlog));
// Informe [equip] · ordre: data · format: detallat · sense fetes · ample 80 · marcant vençudes

console.log(generarInforme(backlog, { responsable: 'Iván', incloureFetes: true }));
// Informe [Iván] · ordre: data · format: detallat · amb fetes · ample 80 · marcant vençudes

console.log(generarInforme(backlog, { ordenarPer: 'prioritat', format: 'compacte' }));
// Informe [equip] · ordre: prioritat · format: compacte · sense fetes · ample 80 · marcant vençudes

Comentari: tasques continua sent posicional perquè és la dada principal i sempre hi és. Tota la resta és configuració opcional i va a l'objecte. Fixa't que cap de les tres crides no necessita escriure undefined ni recordar un ordre.

Exercici 3

function resumirBacklog(estats, hores, ...responsablesFiltre) {
  const filtrar = responsablesFiltre.length > 0;

  let tasques = 0;
  let horesTotals = 0;
  let horesObertes = 0;
  let horesFetes = 0;

  for (let i = 0; i < estats.length; i++) {
    if (filtrar) {
      let coincideix = false;
      for (const nom of responsablesFiltre) {
        if (responsables[i] === nom) { coincideix = true; break; }
      }
      if (!coincideix) continue;
    }

    tasques++;
    horesTotals += hores[i];
    if (estats[i] === 'feta') horesFetes += hores[i];
    else horesObertes += hores[i];
  }

  const percentatge = horesTotals === 0
    ? 0
    : Math.round((horesFetes / horesTotals) * 1000) / 10;

  return {
    tasques: tasques,
    horesTotals: horesTotals,
    horesObertes: horesObertes,
    percentatgeCompletat: percentatge
  };
}

console.log(resumirBacklog(estats, hores));
// { tasques: 6, horesTotals: 48, horesObertes: 45, percentatgeCompletat: 6.3 }

console.log(resumirBacklog(estats, hores, 'Iván'));
// { tasques: 3, horesTotals: 25, horesObertes: 25, percentatgeCompletat: 0 }

console.log(resumirBacklog(estats, hores, 'Marta', 'Lucía'));
// { tasques: 3, horesTotals: 23, horesObertes: 20, percentatgeCompletat: 13 }

Comentari: responsablesFiltre.length > 0 distingeix "sense filtre" de "amb filtre" sense necessitat d'un paràmetre extra. La guarda horesTotals === 0 evita el NaN de dividir entre zero, un cas que apareix tan bon punt el filtre no troba ningú. I el truc Math.round(x * 1000) / 10 arrodoneix el percentatge a un decimal.

Conclusió

Ara domines els dos extrems d'una funció. Per l'entrada: saps que els arguments s'aparellen per posició, que els que falten valen undefined i que els que sobren s'ignoren; que els primitius es passen per valor i els objectes per valor de la referència, amb la conseqüència que mutar una propietat d'un argument que es veu a fora mentre que reassignar el paràmetre no; que els paràmetres per defecte s'activen només amb undefined, s'avaluen a cada crida i poden dependre dels paràmetres anteriors; que ...args substitueix el vell arguments amb un array de debò; i que a partir de tres o quatre dades convé passar un objecte d'opcions amb opcions = {} per defecte.

Per la sortida: saps retornar aviat amb guardes, mantenir un únic tipus de retorn, triar entre null, un valor neutre o un throw per representar l'absència, i empaquetar diversos resultats en un objecte (o en un array quan l'ordre és evident), a l'espera de la desestructuració del Mòdul 4. I tens el criteri més important de tots: una funció pura —mateix resultat amb els mateixos arguments, sense efectes secundaris— és fàcil de provar, de raonar i de reutilitzar, així que l'estratègia és un nucli pur gran i una capa fina d'efectes. crearTasca() i resumirCarrega() de Nómada Tasques en són l'aplicació directa.

Queda un cap solt que has fregat diverses vegades sense anomenar-lo. Quan crearTasca fa ultimId = ultimId + 1, està tocant una variable que viu fora d'ella. Per què la pot veure? On viu exactament cada variable? Què passa si dins d'una funció declaro una altra variable amb el mateix nom? I sobretot: es pot tenir un comptador d'ids que ningú més no pugui tocar per accident, sense deixar-lo solt al fitxer? Les respostes són a Àmbit i Closures, on construiràs crearGeneradorIds() i descobriràs que una funció pot recordar l'entorn en què va néixer.

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