Portes mitja dotzena de lliçons fregant aquesta idea sense posar-li nom. A Expressions de Funció i Funcions Fletxa vas desar funcions dins d'un objecte de formatadors i d'un array de validadors. A Àmbit i Closures vas escriure llistarSegons(filtre), que rebia una funció, i crearFiltrePerResponsable(nom), que en retornava una. Aquest nom és funció d'ordre superior, i és la tècnica que converteix el codi repetitiu en codi configurable. En aquesta lliçó l'estudiaràs a fons: construiràs les teves pròpies versions de forEach, map, filter i reduce per entendre exactament què fan per dins, aprendràs a compondre funcions en canonades i acabaràs muntant un motor d'informes per a Taller Nómada al qual s'endollen un filtre, un transformador i un agregador diferents segons el que demani la Marta.
Avís de progressió: els arrays de JavaScript ja porten
forEach,map,filterireducede sèrie. Aquí els implementaràs a mà per entendre el mecanisme; els mètodes natius, amb tota la seva potència i els seus matisos, arriben a Iteració sobre Arrays i Cercar, Ordenar i Agregar Dades. Fins aleshores, continuem amb bucles i arrays paral·lels.
Contingut
- Què és una funció d'ordre superior
- Callbacks: el vocabulari
elMeuForEach: fer alguna cosa amb cada elementelMeuMap: transformar cada elementelMeuFilter: quedar-se amb algunselMeuReduce: reduir-ho tot a un valor- Encadenar les quatre sobre el backlog
- Funcions que retornen funcions: fàbriques i currying
- Composició de funcions:
compondreipipe - Funcions com a configuració
- Exemple integrador: el motor d'informes de Taller Nómada
- Una nota sobre llegibilitat
- Errors Habituals i Consells
- Exercicis
- Conclusió
- Què és una funció d'ordre superior
La definició és curta i precisa:
Una funció d'ordre superior (higher-order function, HOF) és una funció que rep una o més funcions com a argument, retorna una funció, o totes dues coses.
Tota la resta és una funció normal, o de primer ordre. I això només és possible perquè, com vas veure a 03-02, en JavaScript les funcions són valors.
// D'ordre superior: REP una funció
function repetir(vegades, accio) {
for (let i = 0; i < vegades; i++) {
accio(i);
}
}
repetir(3, (n) => console.log(`Recordatori ${n + 1} a Iván`));
// Recordatori 1 a Iván
// Recordatori 2 a Iván
// Recordatori 3 a Iván
// D'ordre superior: RETORNA una funció
function crearRecordatori(persona) {
return (assumpte) => `Recordatori per a ${persona}: ${assumpte}`;
}
const avisarLucia = crearRecordatori('Lucía');
console.log(avisarLucia("el web de reserves venç el 2 d'octubre"));
// Recordatori per a Lucía: el web de reserves venç el 2 d'octubreQuè s'hi guanya? Separar l'esquelet del detall. repetir sap repetir, però no sap què cal fer a cada volta: això li ho dius tu. El mateix esquelet serveix per imprimir, per acumular o per validar.
flowchart LR
A["Esquelet fix<br/>(recórrer, repetir, ordenar)"] --> C["Funció d'ordre superior"]
B["Detall variable<br/>(què fer amb cada element)"] --> C
C --> D["Comportament concret"]
- Callbacks: el vocabulari
Una funció que es passa com a argument perquè una altra la cridi s'anomena callback (funció de retrucada). El vocabulari associat, que convé fer servir amb precisió:
| Terme | Significat | A l'exemple |
|---|---|---|
| Funció d'ordre superior | La que rep o retorna funcions | repetir |
| Callback | La funció que es passa per ser cridada | (n) => console.log(...) |
| Invocar el callback | Cridar-lo des de dins de la HOF | accio(i) |
| Signatura del callback | Quins paràmetres rebrà i què ha de retornar | (element, index) => ... |
| Predicat | Callback que retorna true/false |
(h) => h > 10 |
| Transformador | Callback que retorna un valor nou | (h) => h * 2 |
| Comparador | Callback que retorna -1, 0 o 1 |
(a, b) => a - b |
| Reductor | Callback que combina acumulador i element | (acc, h) => acc + h |
Un punt que confon al principi: qui defineix el callback no decideix quan s'executa ni amb quins arguments. Això ho decideix la funció d'ordre superior. Tu escrius el "què fer"; ella hi posa el "quan" i el "amb què".
function ambCadaHora(hores, callback) {
for (let i = 0; i < hores.length; i++) {
callback(hores[i], i); // ← la HOF decideix: dos arguments, en aquest ordre
}
}
ambCadaHora([12, 6, 14], (h, i) => console.log(`Tasca ${i + 1}: ${h} h`));
ambCadaHora([12, 6, 14], (h) => console.log(h)); // pots ignorar el segonPer això, abans d'escriure un callback, cal saber quina signatura espera la funció que el rebrà. És el primer que es mira a la documentació de qualsevol mètode.
elMeuForEach: fer alguna cosa amb cada element
elMeuForEach: fer alguna cosa amb cada elementEl més senzill: recorre l'array i crida el callback amb cada element. No retorna res; el seu valor és a l'efecte que produeix.
'use strict';
/**
* Crida `accio` amb cada element de l'array.
* @param {Array} llista array a recórrer
* @param {Function} accio callback (element, index, llista)
*/
function elMeuForEach(llista, accio) {
for (let i = 0; i < llista.length; i++) {
accio(llista[i], i, llista);
}
}
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'
];
elMeuForEach(titols, (titol, i) => {
console.log(`${i + 1}. ${titol}`);
});
// 1. Redissenyar la sala polivalent
// 2. Cartelleria del taller de serigrafia
// ...Els tres arguments del callback (element, index, llista) no són un caprici: és exactament la signatura que fa servir el forEach natiu, així que aprendre-la ara t'estalvia feina al Mòdul 4.
Comparat amb un for clàssic, el que aporta és que desapareix la maquinària del bucle: no hi ha i = 0, ni i < llista.length, ni i++, i per tant no hi ha errors d'índex fora de rang.
Una diferència important amb el for: des d'un callback no es pot fer break. Un return dins del callback acaba el callback, no el recorregut.
elMeuForEach(titols, (titol) => {
if (titol.startsWith('Inventari')) return; // salta AQUEST, no talla el recorregut
console.log(titol);
});Si necessites tallar, fes servir un for amb break o una funció pensada per cercar (ho veuràs a 04-05 amb find i some).
elMeuMap: transformar cada element
elMeuMap: transformar cada elementmap construeix un array nou de la mateixa mida, on cada element és el resultat d'aplicar el callback a l'original.
/**
* Retorna un array nou amb el resultat de transformar cada element.
* @param {Array} llista
* @param {Function} transformar callback (element, index, llista) → valor nou
* @returns {Array} array nou de la mateixa mida
*/
function elMeuMap(llista, transformar) {
const resultat = [];
for (let i = 0; i < llista.length; i++) {
resultat.push(transformar(llista[i], i, llista));
}
return resultat;
}
const hores = [12, 6, 14, 3, 8, 5];
const enMinuts = elMeuMap(hores, (h) => h * 60);
console.log(enMinuts); // [ 720, 360, 840, 180, 480, 300 ]
console.log(hores); // [ 12, 6, 14, 3, 8, 5 ] ← intacte
const numerades = elMeuMap(titols, (t, i) => `${i + 1}. ${t}`);
console.log(numerades[5]); // 6. Pressupost de la fusteriaTres propietats que defineixen map i que convé gravar:
| Propietat | Conseqüència |
|---|---|
| Retorna un array nou | L'original no es toca: és una operació pura |
| Mateixa mida que l'original | Si n'entren 6, en surten 6 (per filtrar hi ha filter) |
| Un a un, en ordre | L'element i del resultat ve del i de l'original |
L'error més comú és fer servir map quan en realitat vols forEach:
// ✗ map el resultat del qual es llença: fes servir forEach
elMeuMap(titols, (t) => console.log(t)); // retorna [undefined × 6]
// ✓
elMeuForEach(titols, (t) => console.log(t));Regla: si faràs servir l'array resultant, map; si només vols l'efecte, forEach.
elMeuFilter: quedar-se amb alguns
elMeuFilter: quedar-se amb algunsfilter construeix un array nou amb els elements per als quals el callback (un predicat) retorna un valor truthy.
/**
* Retorna un array nou amb els elements que compleixen el predicat.
* @param {Array} llista
* @param {Function} predicat callback (element, index, llista) → booleà
* @returns {Array} array nou, de 0 a llista.length elements
*/
function elMeuFilter(llista, predicat) {
const resultat = [];
for (let i = 0; i < llista.length; i++) {
if (predicat(llista[i], i, llista)) {
resultat.push(llista[i]);
}
}
return resultat;
}
const llargues = elMeuFilter(hores, (h) => h > 8);
console.log(llargues); // [ 12, 14 ]
const deSerigrafia = elMeuFilter(titols, (t) => t.toLowerCase().includes('serigrafia'));
console.log(deSerigrafia);
// [ 'Cartelleria del taller de serigrafia', 'Inventari de tintes de serigrafia' ]Amb arrays paral·lels, el truc és filtrar índexs en lloc de valors, per poder consultar després la resta dels arrays:
const responsables = ['Iván', 'Marta', 'Lucía', 'Marta', 'Iván', 'Iván'];
const estats = ['en-curs', 'pendent', 'pendent', 'feta', 'en-curs', 'pendent'];
const indexs = [0, 1, 2, 3, 4, 5];
const obertesDeIvan = elMeuFilter(indexs, (i) => responsables[i] === 'Iván' && estats[i] !== 'feta');
console.log(obertesDeIvan); // [ 0, 4, 5 ]
elMeuForEach(obertesDeIvan, (i) => console.log(`${titols[i]} — ${hores[i]} h`));
// Redissenyar la sala polivalent — 12 h
// Guia sobre enquadernació per a residents — 8 h
// Pressupost de la fusteria — 5 hAquest patró "filtrar índexs" és un apedaçament necessari mentre les dades visquin en arrays paral·lels. Al Mòdul 4, amb un array d'objectes, s'escriurà directament sobre les tasques.
elMeuMap |
elMeuFilter |
|
|---|---|---|
| Mida del resultat | Igual que l'original | Entre 0 i l'original |
| El callback retorna | Un valor nou | Un booleà |
| Els elements del resultat | Transformats | Els originals, sense tocar |
| Nom del callback | Transformador | Predicat |
elMeuReduce: reduir-ho tot a un valor
elMeuReduce: reduir-ho tot a un valorreduce és la més potent i la que més costa. Recorre l'array mantenint un acumulador que es va actualitzant amb cada element, i al final retorna aquest acumulador. El resultat pot ser un nombre, un text, un array o un objecte: qualsevol cosa.
/**
* Redueix l'array a un únic valor.
* @param {Array} llista
* @param {Function} reductor callback (acumulador, element, index, llista) → nou acumulador
* @param {*} valorInicial valor de partida de l'acumulador
*/
function elMeuReduce(llista, reductor, valorInicial) {
let acumulador = valorInicial;
for (let i = 0; i < llista.length; i++) {
acumulador = reductor(acumulador, llista[i], i, llista);
}
return acumulador;
}Tot el mecanisme cap en quatre línies, però convé veure'l pas a pas amb la suma d'hores del backlog:
| Volta | acc en entrar |
h |
acc + h |
|---|---|---|---|
| 0 | 0 (inicial) | 12 | 12 |
| 1 | 12 | 6 | 18 |
| 2 | 18 | 14 | 32 |
| 3 | 32 | 3 | 35 |
| 4 | 35 | 8 | 43 |
| 5 | 43 | 5 | 48 |
I ara els casos que demostren que reduce no és només "sumar":
// Màxim
const maxHores = elMeuReduce(hores, (acc, h) => (h > acc ? h : acc), hores[0]);
console.log(maxHores); // 14
// Concatenar text
const llistat = elMeuReduce(titols, (acc, t) => `${acc}\n· ${t}`, 'Backlog:');
console.log(llistat);
// Backlog:
// · Redissenyar la sala polivalent
// · ...
// Comptar per categoria → retorna un OBJECTE
const perResponsable = elMeuReduce(responsables, (acc, nom) => {
acc[nom] = (acc[nom] ?? 0) + 1;
return acc;
}, {});
console.log(perResponsable); // { 'Iván': 3, Marta: 2, 'Lucía': 1 }
// Esforç ponderat (la regla que arrossegues des del Mòdul 2)
const PESOS = { alta: 3, mitjana: 2, baixa: 1 };
const prioritats = ['alta', 'mitjana', 'alta', 'baixa', 'mitjana', 'alta'];
const indexs2 = [0, 1, 2, 3, 4, 5];
const esforc = elMeuReduce(indexs2, (acc, i) => acc + (PESOS[prioritats[i]] ?? 0) * hores[i], 0);
console.log(esforc); // 124Dos advertiments sobre reduce:
- El reductor ha de retornar sempre l'acumulador. Oblidar el
returnés l'error número u; l'acumulador passa a serundefinedi tot peta a la volta següent. - Dóna sempre un valor inicial. Sense ell, el natiu fa servir el primer element com a acumulador i falla amb arrays buits. El nostre
elMeuReducesimplement retornariaundefined.
flowchart LR
A["inicial: 0"] --> B["+12 → 12"] --> C["+6 → 18"] --> D["+14 → 32"]
D --> E["+3 → 35"] --> F["+8 → 43"] --> G["+5 → 48"]
- Encadenar les quatre sobre el backlog
Amb les quatre peces ja es poden expressar preguntes de negoci completes de manera gairebé declarativa. Pregunta de la Marta: quantes hores obertes de prioritat alta hi ha al backlog, i quines són?
'use strict';
const AVUI = '2026-09-20';
const indexs = [0, 1, 2, 3, 4, 5];
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'];
// 1. Filtrar: prioritat alta i no acabades
const altesObertes = elMeuFilter(indexs, (i) => prioritats[i] === 'alta' && estats[i] !== 'feta');
console.log(altesObertes); // [ 0, 2, 5 ]
// 2. Transformar: una línia llegible per tasca
const linies = elMeuMap(altesObertes, (i) => {
const avis = datesLimit[i] < AVUI ? ' ⚠ VENÇUDA' : '';
return `${titols[i]} · ${responsables[i]} · ${hores[i]} h${avis}`;
});
// 3. Reduir: total d'hores
const horesAltes = elMeuReduce(altesObertes, (acc, i) => acc + hores[i], 0);
// 4. Efecte: imprimir
elMeuForEach(linies, (linia) => console.log(` ${linia}`));
console.log(`Total d'hores obertes en prioritat alta: ${horesAltes} h`);Sortida:
Redissenyar la sala polivalent · Iván · 12 h Actualitzar el web de reserves · Lucía · 14 h Pressupost de la fusteria · Iván · 5 h ⚠ VENÇUDA Total d'hores obertes en prioritat alta: 31 h
Observa l'estructura: filtrar → transformar → agregar → efecte. Cada pas és una operació amb nom, es pot llegir per separat i es pot canviar sense tocar els altres. Comparat amb el bucle únic del Mòdul 2 —que feia les quatre coses barrejades dins d'un if— la diferència de llegibilitat és enorme.
- Funcions que retornen funcions: fàbriques i currying
Ja vas conèixer les fàbriques a 03-04. Aquí les portem un pas més enllà.
// Fàbrica de predicats per camp i valor
function crearPredicat(arrayCamp, valorEsperat) {
return (index) => arrayCamp[index] === valorEsperat;
}
const esDeIvan = crearPredicat(responsables, 'Iván');
const estaPendent = crearPredicat(estats, 'pendent');
const esAlta = crearPredicat(prioritats, 'alta');
console.log(elMeuFilter(indexs, esDeIvan)); // [ 0, 4, 5 ]
console.log(elMeuFilter(indexs, estaPendent)); // [ 1, 2, 5 ]I combinadors que produeixen predicats nous a partir d'altres:
const tots = (...predicats) => (i) => {
for (const p of predicats) {
if (!p(i)) return false;
}
return true;
};
const algun = (...predicats) => (i) => {
for (const p of predicats) {
if (p(i)) return true;
}
return false;
};
const no = (predicat) => (i) => !predicat(i);
console.log(elMeuFilter(indexs, tots(esDeIvan, esAlta))); // [ 0, 5 ]
console.log(elMeuFilter(indexs, algun(esAlta, estaPendent))); // [ 0, 1, 2, 5 ]
console.log(elMeuFilter(indexs, no(crearPredicat(estats, 'feta')))); // [ 0, 1, 2, 4, 5 ]Amb tres funcions de cinc línies has construït un petit llenguatge de consultes sobre el backlog. Això és poder expressiu.
Currying
Currying és transformar una funció de diversos paràmetres en una cadena de funcions d'un paràmetre cadascuna:
// Versió normal
function esforc(pes, hores) {
return pes * hores;
}
// Versió "currificada"
const esforcCurry = (pes) => (hores) => pes * hores;
console.log(esforc(3, 12)); // 36
console.log(esforcCurry(3)(12)); // 36
// La gràcia és fixar el primer argument
const esforcAlta = esforcCurry(3);
console.log(esforcAlta(12)); // 36
console.log(esforcAlta(14)); // 42
console.log(esforcAlta(5)); // 15La tècnica emparentada, l'aplicació parcial, fixa alguns arguments i deixa la resta:
function ambMarge(limit, horesObertes) {
return limit - horesObertes;
}
const margeSetmanal = (horesObertes) => ambMarge(40, horesObertes); // R7 fixada
console.log(margeSetmanal(25)); // 15
console.log(margeSetmanal(45)); // -5Quan val la pena? Quan cridaràs moltes vegades amb el mateix primer argument, o quan necessites una funció d'un sol paràmetre per passar-la a elMeuMap o elMeuFilter. Fora d'aquests casos, currificar per currificar fa el codi més difícil de llegir.
- Composició de funcions:
compondre i pipe
compondre i pipeSi tens diverses transformacions petites, encadenar-les a mà és incòmode:
const treureEspais = (t) => t.trim();
const aMinuscules = (t) => t.toLowerCase();
const senseAccents = (t) => t.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
const aGuionets = (t) => t.replace(/\s+/g, '-');
// Imbricat: es llegeix de dins cap enfora, al revés de com passa
const slug = aGuionets(senseAccents(aMinuscules(treureEspais(' Pressupost de la Fusteria '))));
console.log(slug); // pressupost-de-la-fusteriaCompondre és crear una funció nova que n'aplica diverses en cadena:
/** compondre(f, g)(x) === f(g(x)) — s'aplica de dreta a esquerra */
function compondre(f, g) {
return (x) => f(g(x));
}
const netejar = compondre(aMinuscules, treureEspais);
console.log(netejar(' ETIQUETA DE SERIGRAFIA ')); // etiqueta de serigrafiaI pipe fa el mateix però en l'ordre en què es llegeix, d'esquerra a dreta, que és molt més natural:
/** pipe(f, g, h)(x) === h(g(f(x))) — s'aplica d'esquerra a dreta */
function pipe(...funcions) {
return function (valorInicial) {
let valor = valorInicial;
for (const fn of funcions) {
valor = fn(valor);
}
return valor;
};
}
const aEtiqueta = pipe(treureEspais, aMinuscules, senseAccents, aGuionets);
console.log(aEtiqueta(' Pressupost de la Fusteria ')); // pressupost-de-la-fusteria
console.log(aEtiqueta(' Serigrafia ')); // serigrafia
console.log(aEtiqueta('Enquadernació Manual')); // enquadernacio-manualFixa't que pipe és literalment elMeuReduce aplicat a funcions en lloc de a dades: parteix d'un valor inicial i el va transformant. L'aplicació directa al projecte és la R9 (etiquetes en minúscules, sense duplicats):
const etiquetesCrues = [' Fusteria ', 'FUSTERIA', 'Enquadernació', 'enquadernació ', 'Disseny'];
const normalitzades = elMeuMap(etiquetesCrues, pipe(treureEspais, aMinuscules, senseAccents));
console.log(normalitzades);
// [ 'fusteria', 'fusteria', 'enquadernacio', 'enquadernacio', 'disseny' ]
const senseDuplicats = elMeuReduce(normalitzades, (acc, e) => {
if (!acc.includes(e)) acc.push(e);
return acc;
}, []);
console.log(senseDuplicats); // [ 'fusteria', 'enquadernacio', 'disseny' ]flowchart LR
A["' Pressupost de la Fusteria '"] --> B["treureEspais"] --> C["aMinuscules"]
C --> D["senseAccents"] --> E["aGuionets"] --> F["'pressupost-de-la-fusteria'"]
- Funcions com a configuració
El tercer gran ús de les HOF és parametritzar el comportament d'un component. En lloc d'escriure una funció diferent per a cada variant, n'escrius una i li passes la peça que canvia.
// Un únic "ordenador" que accepta qualsevol comparador
function ordenarIndexs(indexs, comparar) {
const copia = [];
for (const i of indexs) copia.push(i);
// Ordenació per inserció, per no dependre encara de sort() (arriba a 04-05)
for (let i = 1; i < copia.length; i++) {
const actual = copia[i];
let j = i - 1;
while (j >= 0 && comparar(copia[j], actual) > 0) {
copia[j + 1] = copia[j];
j--;
}
copia[j + 1] = actual;
}
return copia;
}
const perData = (a, b) => (datesLimit[a] < datesLimit[b] ? -1 : datesLimit[a] > datesLimit[b] ? 1 : 0);
const perHoresDesc = (a, b) => hores[b] - hores[a];
const perPrioritat = (a, b) => (PESOS[prioritats[b]] ?? 0) - (PESOS[prioritats[a]] ?? 0);
console.log(elMeuMap(ordenarIndexs(indexs, perData), (i) => `${datesLimit[i]} ${titols[i]}`));
// [ '2026-09-05 Pressupost de la fusteria',
// '2026-09-12 Inventari de tintes de serigrafia',
// '2026-09-30 Redissenyar la sala polivalent', ... ]
console.log(elMeuMap(ordenarIndexs(indexs, perHoresDesc), (i) => `${hores[i]} h`));
// [ '14 h', '12 h', '8 h', '6 h', '5 h', '3 h' ]El mateix principi s'aplica a validadors i formatadors intercanviables:
function validarAmb(regles, index) {
const errors = [];
elMeuForEach(regles, (regla) => {
if (!regla.comprovar(index)) errors.push(`${regla.codi}: ${regla.missatge}`);
});
return errors;
}
const reglesAlta = [
{ codi: 'R2', missatge: 'El títol és obligatori.', comprovar: (i) => titols[i].trim().length > 0 },
{ codi: 'R3', missatge: 'Les hores han de ser entre 0 i 40.', comprovar: (i) => hores[i] > 0 && hores[i] <= 40 },
{ codi: 'R10', missatge: 'La tasca està vençuda.', comprovar: (i) => !(datesLimit[i] < AVUI && estats[i] !== 'feta') }
];
console.log(validarAmb(reglesAlta, 5));
// [ 'R10: La tasca està vençuda.' ]
console.log(validarAmb(reglesAlta, 0));
// []Fixa't en el patró, perquè és el mateix que estructura gairebé tot el programari professional: un motor genèric + un conjunt de funcions endollables. Afegir una regla, un ordre o un format ja no vol dir tocar el motor.
- Exemple integrador: el motor d'informes de Taller Nómada
Ajuntem tot en una sola peça. generarInforme no sap res de tasques: sap filtrar, transformar, agregar i presentar, i rep cadascuna d'aquestes quatre coses com a funció.
'use strict';
/**
* Motor d'informes genèric.
* @param {Array} font elements de partida (aquí, índexs del backlog)
* @param {Object} configuracio
* @param {string} configuracio.titol
* @param {Function} configuracio.filtre (element) → booleà
* @param {Function} configuracio.transformar (element) → línia de text
* @param {Function} configuracio.agregar (elements) → resum
* @param {Function} [configuracio.ordenar] comparador opcional
*/
function generarInforme(font, configuracio) {
const filtre = configuracio.filtre ?? (() => true);
const transformar = configuracio.transformar ?? ((x) => String(x));
const agregar = configuracio.agregar ?? (() => '');
const ordenar = configuracio.ordenar ?? null;
let seleccionats = elMeuFilter(font, filtre);
if (ordenar !== null) seleccionats = ordenarIndexs(seleccionats, ordenar);
const linies = elMeuMap(seleccionats, transformar);
const sortida = [`── ${configuracio.titol} ──`];
elMeuForEach(linies, (linia) => sortida.push(` ${linia}`));
sortida.push(` ${agregar(seleccionats)}`);
return elMeuReduce(sortida, (acc, l) => `${acc}\n${l}`, '').trim();
}
// ─── Peces reutilitzables ──────────────────────────────────────────
const noFeta = (i) => estats[i] !== 'feta';
const vencuda = (i) => datesLimit[i] < AVUI && estats[i] !== 'feta';
const deResponsable = (nom) => (i) => responsables[i] === nom;
const liniaDetallada = (i) =>
`${titols[i]} · ${responsables[i]} · ${prioritats[i]} · ${hores[i]} h · venç ${datesLimit[i]}`;
const liniaCompacta = (i) => `${titols[i]} (${hores[i]} h)`;
const sumaHores = (seleccio) => `Total: ${elMeuReduce(seleccio, (acc, i) => acc + hores[i], 0)} h`;
const recompteIPes = (seleccio) => {
const n = seleccio.length;
const pes = elMeuReduce(seleccio, (acc, i) => acc + (PESOS[prioritats[i]] ?? 0) * hores[i], 0);
return `${n} tasca(s) · esforç ponderat ${pes}`;
};
// ─── Informe 1: tot el que és obert, per data ──────────────────────
console.log(generarInforme(indexs, {
titol: 'Feina oberta per data límit',
filtre: noFeta,
ordenar: perData,
transformar: liniaDetallada,
agregar: sumaHores
}));── Feina oberta per data límit ── Pressupost de la fusteria · Iván · alta · 5 h · venç 2026-09-05 Redissenyar la sala polivalent · Iván · alta · 12 h · venç 2026-09-30 Actualitzar el web de reserves · Lucía · alta · 14 h · venç 2026-10-02 Cartelleria del taller de serigrafia · Marta · mitjana · 6 h · venç 2026-10-15 Guia sobre enquadernació per a residents · Iván · mitjana · 8 h · venç 2026-11-05 Total: 45 h
// ─── Informe 2: la càrrega de l'Iván, compacta ─────────────────────
console.log(generarInforme(indexs, {
titol: 'Càrrega oberta: Iván',
filtre: tots(deResponsable('Iván'), noFeta),
ordenar: perHoresDesc,
transformar: liniaCompacta,
agregar: recompteIPes
}));── Càrrega oberta: Iván ── Redissenyar la sala polivalent (12 h) Guia sobre enquadernació per a residents (8 h) Pressupost de la fusteria (5 h) 3 tasca(s) · esforç ponderat 67
// ─── Informe 3: alertes de venciment ───────────────────────────────
console.log(generarInforme(indexs, {
titol: '⚠ Tasques vençudes',
filtre: vencuda,
transformar: (i) => `${titols[i]} — responsable: ${responsables[i]} — va vèncer el ${datesLimit[i]}`,
agregar: (seleccio) => `${seleccio.length} tasca(s) requereixen atenció immediata`
}));── ⚠ Tasques vençudes ── Pressupost de la fusteria — responsable: Iván — va vèncer el 2026-09-05 1 tasca(s) requereixen atenció immediata
Tres informes completament diferents, zero canvis al motor. Això és el que compren les funcions d'ordre superior: quan arribi el Mòdul 6 i la Marta vulgui filtrar el backlog des d'un desplegable de la interfície, l'única cosa que canviarà és quina funció es passa com a filtre.
- Una nota sobre llegibilitat
Tot l'anterior és potent, i precisament per això convé dir quan no fer-ho servir. L'estil funcional té un punt de rendiments decreixents.
// ✗ Il·legible: currying + composició + reduce imbricat per a una cosa trivial
const total = pipe(
(xs) => elMeuFilter(xs, (i) => estats[i] !== 'feta'),
(xs) => elMeuReduce(xs, (a, i) => a + hores[i], 0)
)(indexs);
// ✓ Igual de correcte i molt més clar
let total = 0;
for (let i = 0; i < hores.length; i++) {
if (estats[i] !== 'feta') total += hores[i];
}Criteris per decidir:
| Fes servir HOF quan… | Fes servir un bucle normal quan… |
|---|---|
| L'operació es repeteix amb variants | És un càlcul puntual i simple |
| El "què fer" ha de poder canviar | El comportament és fix |
| Vols anomenar cada pas (filtrar, transformar) | El bucle cap en quatre línies llegibles |
| Reutilitzaràs les peces | Només es fa servir un cop |
| Talles la lògica en trossos provables | Necessites break, continue o índexs retorçats |
I una regla d'or: el codi s'escriu una vegada i es llegeix cinquanta. Si la teva companya ha de dibuixar un diagrama per entendre una línia, aquesta línia està malament, per elegant que sigui.
Errors Habituals i Consells
1. Passar el resultat en lloc de la funció.
elMeuFilter(indexs, esDeIvan(0)); // ✗ passa `true`, no una funció
elMeuFilter(indexs, esDeIvan); // ✓Símptoma: TypeError: predicat is not a function.
2. Oblidar el return al reductor.
elMeuReduce(responsables, (acc, n) => { acc[n] = (acc[n] ?? 0) + 1; }, {});
// ✗ TypeError: Cannot set properties of undefinedAmb cos de bloc, return acc; és obligatori.
3. Fer servir map pel seu efecte secundari. Si no fas servir l'array retornat, volies forEach.
4. Intentar tallar un forEach amb break. No és possible. Fes servir un for clàssic.
5. Mutar l'array original dins d'un callback. Modificar la llista mentre es recorre produeix salts i elements repetits. Treballa sempre sobre el resultat.
6. Callbacks amb efectes secundaris ocults. Un predicat que a més imprimeix o modifica alguna cosa fa impossible raonar sobre el filtre. Els predicats han de ser purs (03-03).
7. Consell: anomena els callbacks que es repeteixen. elMeuFilter(indexs, noFeta) es llegeix infinitament millor que elMeuFilter(indexs, (i) => estats[i] !== 'feta') repetit set vegades.
8. Consell: documenta la signatura esperada. Quan escriguis una HOF, deixa clar en un comentari què rebrà el callback i què ha de retornar. És el primer que necessita qui la faci servir.
Exercicis
Exercici 1 — elMeuSome, elMeuEvery i elMeuFind
Implementa tres funcions d'ordre superior més, amb la mateixa signatura de callback (element, index, llista):
elMeuSome(llista, predicat):truesi almenys un compleix; talla tan bon punt en troba un.elMeuEvery(llista, predicat):truesi tots compleixen; talla tan bon punt en falla un.elMeuFind(llista, predicat): retorna el primer element que compleix, oundefined.
Fes-les servir per respondre: hi ha alguna tasca vençuda? Estan totes assignades? Quina és la primera tasca de més de 10 h?
Exercici 2 — agruparPer
Escriu agruparPer(llista, obtenirClau), una funció d'ordre superior que retorni un objecte on cada clau és el resultat d'obtenirClau(element) i cada valor és un array amb els elements d'aquest grup. Fes-la servir per agrupar els índexs del backlog per responsable i per estat, i després calcula les hores obertes de cada grup.
Exercici 3 — Motor d'informes ampliat
Amplia generarInforme perquè accepti dues opcions més:
limit: nombre màxim de línies a mostrar (si es retalla, afegeix una línia… i N més).formatCapcalera: una funció(titol, total) => stringque construeixi la capçalera.
Genera-hi un informe "Top 3 per hores" de la feina oberta.
Solucions
Exercici 1
function elMeuSome(llista, predicat) {
for (let i = 0; i < llista.length; i++) {
if (predicat(llista[i], i, llista)) return true; // talla aquí
}
return false;
}
function elMeuEvery(llista, predicat) {
for (let i = 0; i < llista.length; i++) {
if (!predicat(llista[i], i, llista)) return false; // talla aquí
}
return true;
}
function elMeuFind(llista, predicat) {
for (let i = 0; i < llista.length; i++) {
if (predicat(llista[i], i, llista)) return llista[i];
}
return undefined;
}
console.log(elMeuSome(indexs, vencuda)); // true
console.log(elMeuEvery(indexs, (i) => responsables[i] !== null)); // true
console.log(elMeuFind(indexs, (i) => hores[i] > 10)); // 0
const trobada = elMeuFind(indexs, (i) => hores[i] > 10);
console.log(titols[trobada]); // Redissenyar la sala polivalentComentari: fixa't que elMeuSome i elMeuEvery sí que poden tallar, perquè el return és al bucle de la funció d'ordre superior, no al callback. Aquesta és la diferència amb elMeuForEach. elMeuFind retorna undefined quan no troba res, no -1 ni null: és coherent amb el que fa el mètode natiu que veuràs a 04-05.
Exercici 2
function agruparPer(llista, obtenirClau) {
return elMeuReduce(llista, (grups, element, index) => {
const clau = obtenirClau(element, index, llista);
if (!(clau in grups)) grups[clau] = [];
grups[clau].push(element);
return grups; // ← imprescindible
}, {});
}
const perResponsable = agruparPer(indexs, (i) => responsables[i]);
console.log(perResponsable);
// { 'Iván': [ 0, 4, 5 ], Marta: [ 1, 3 ], 'Lucía': [ 2 ] }
const perEstat = agruparPer(indexs, (i) => estats[i]);
console.log(perEstat);
// { 'en-curs': [ 0, 4 ], pendent: [ 1, 2, 5 ], feta: [ 3 ] }
// Hores obertes per responsable
const noms = ['Iván', 'Marta', 'Lucía'];
elMeuForEach(noms, (nom) => {
const grup = perResponsable[nom] ?? [];
const obertes = elMeuReduce(elMeuFilter(grup, noFeta), (acc, i) => acc + hores[i], 0);
console.log(`${nom}: ${obertes} h obertes · marge ${40 - obertes} h`);
});
// Iván: 25 h obertes · marge 15 h
// Marta: 6 h obertes · marge 34 h
// Lucía: 14 h obertes · marge 26 hComentari: agruparPer és una HOF que rep un extractor de clau i retorna un objecte de grups. És una de les funcions auxiliars més útils que existeixen i apareix a totes les llibreries d'utilitats. El return grups del reductor és el punt on més gent s'equivoca.
Exercici 3
function generarInforme(font, configuracio) {
const filtre = configuracio.filtre ?? (() => true);
const transformar = configuracio.transformar ?? ((x) => String(x));
const agregar = configuracio.agregar ?? (() => '');
const ordenar = configuracio.ordenar ?? null;
const limit = configuracio.limit ?? Infinity;
const formatCapcalera = configuracio.formatCapcalera ?? ((t, n) => `── ${t} (${n}) ──`);
let seleccionats = elMeuFilter(font, filtre);
if (ordenar !== null) seleccionats = ordenarIndexs(seleccionats, ordenar);
const total = seleccionats.length;
const visibles = elMeuFilter(seleccionats, (_, index) => index < limit);
const ocultes = total - visibles.length;
const sortida = [formatCapcalera(configuracio.titol, total)];
elMeuForEach(elMeuMap(visibles, transformar), (linia) => sortida.push(` ${linia}`));
if (ocultes > 0) sortida.push(` … i ${ocultes} més`);
sortida.push(` ${agregar(seleccionats)}`);
return elMeuReduce(sortida, (acc, l) => `${acc}\n${l}`, '').trim();
}
console.log(generarInforme(indexs, {
titol: 'Top 3 per hores',
filtre: noFeta,
ordenar: perHoresDesc,
transformar: (i) => `${hores[i]} h — ${titols[i]} (${responsables[i]})`,
agregar: sumaHores,
limit: 3,
formatCapcalera: (t, n) => `▓▓ ${t.toUpperCase()} · ${n} tasques obertes ▓▓`
}));▓▓ TOP 3 PER HORES · 5 tasques obertes ▓▓ 14 h — Actualitzar el web de reserves (Lucía) 12 h — Redissenyar la sala polivalent (Iván) 8 h — Guia sobre enquadernació per a residents (Iván) … i 2 més Total: 45 h
Comentari: l'agregar continua rebent tots els seleccionats, no només els visibles, perquè el total continuï sent el real (45 h) i no el de les tres línies mostrades. És una decisió de disseny que convé fer explícita; la contrària també seria defensable, però cal triar-ne una i documentar-la. Fixa't també en l'ús de _ com a nom del paràmetre que no es fa servir a elMeuFilter(seleccionats, (_, index) => ...): és una convenció molt estesa per dir "aquest argument existeix però l'ignoro".
Conclusió
Ja tens l'eina que converteix codi repetitiu en codi configurable. Una funció d'ordre superior rep funcions, retorna funcions o totes dues coses, i el seu valor és a separar l'esquelet fix —recórrer, filtrar, ordenar, informar— del detall variable que li passes com a callback. Has après el vocabulari amb precisió: callback, predicat, transformador, comparador, reductor, i el fet fonamental que és la funció d'ordre superior, i no pas tu, qui decideix quan s'invoca el callback i amb quins arguments.
Has implementat a mà les quatre operacions fonamentals sobre llistes: elMeuForEach (efecte sobre cada element, sense retorn), elMeuMap (array nou de la mateixa mida amb cada element transformat), elMeuFilter (array nou amb els que compleixen un predicat) i elMeuReduce (tot reduït a un valor mitjançant un acumulador). Saber que caben en quatre o cinc línies cadascuna desmitifica per complet els mètodes natius que estudiaràs a Iteració sobre Arrays i Cercar, Ordenar i Agregar Dades: quan els facis servir, sabràs exactament què està passant per dins.
I has vist els tres patrons que es construeixen a sobre: fàbriques i currying, que fixen arguments i produeixen funcions especialitzades (crearPredicat, tots, algun, no, esforcCurry); composició amb compondre i pipe, que encadena transformacions petites en una canonada llegible —la que normalitza etiquetes segons la R9—; i funcions com a configuració, que van donar lloc al motor d'informes de Taller Nómada: tres informes radicalment diferents sense tocar ni una sola línia del motor. Juntament amb l'advertiment final, que importa tant com la resta: si un bucle de quatre línies és més clar, escriu el bucle.
Queda una última manera de resoldre problemes amb funcions, i és la més desconcertant de totes: una funció que es crida a si mateixa. Ja l'has fregat dues vegades —l'expressió de funció amb nom de 03-02 servia per a això, i el RangeError de 03-05 va aparèixer per una crida que no s'aturava mai—. A Recursivitat hi posaràs mètode: cas base, cas recursiu, la pila de crides dibuixada pas a pas, i el problema real que la justifica a Nómada Tasques: recórrer les subtasques imbricades d'una tasca per sumar-ne les hores.
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
