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
- Arguments de més i de menys
- Pas per valor: els primitius
- Pas per valor de la referència: objectes i arrays
- L'avís sobre mutar els arguments
- Paràmetres per defecte
- Dependències entre paràmetres per defecte
- L'objecte
arguments(llegat) - Paràmetres rest
...args - El patró "objecte d'opcions"
return: retornar aviat i retornar un sol tipus- Retornar diversos valors
- Funcions pures i efectes secundaris
- Cas pràctic:
crearTascairesumirCarrega - Errors Habituals i Consells
- Exercicis
- Conclusió
- 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'ignoraLes 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 - 25Un 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.
- 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); // 30flowchart 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.
- 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 ← intactaflowchart 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) |
Sí |
llista.push(nova) (mutar un array) |
Sí |
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.
- 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.
- 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 hDetall 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 hCompara-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).
- 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/diaA 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 initializationAixò é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: responsableFunciona 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.
- L'objecte
arguments (llegat)
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)); // 48Aquest 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.
- Paràmetres rest
...args
...argsEls 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()); // 0I 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 hComparació directa:
arguments |
...rest |
|
|---|---|---|
| Tipus | Objecte array-like | Array real |
| Funciona a les fletxes | No | Sí |
| Visible a la signatura | No | Sí |
| 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.
- 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
undefinedexplí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ÇUDAAvantatges 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.
return: retornar aviat i retornar un sol tipus
return: retornar aviat i retornar un sol tipus10.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 sentitAlternatives 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.
- 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 | Sí | No, per posició |
| Ordre significatiu | No | Sí |
| 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);
- Funcions pures i efectes secundaris
Una funció és pura si compleix dues condicions:
- Amb els mateixos arguments retorna sempre el mateix resultat.
- 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
- Cas pràctic:
crearTasca i resumirCarrega
crearTasca i resumirCarregaAjuntem 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 0Set 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 ← malamentFes 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: titolEn 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 compacteExercici 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,horesObertesipercentatgeCompletat(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çudaExercici 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çudesComentari: 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 sí 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
- 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
