Vam prometre que l'alleujament arribaria a la primera pàgina, així que comencem per aquí. Durant tot el Mòdul 3 has arrossegat sis arrays paral·lels —titols[i], responsables[i], prioritats[i], estats[i], hores[i], datesLimit[i]— i una funció, descriureTasca, que necessitava vuit arguments per pintar una sola línia. Cada vegada que volies filtrar, filtraves índexs en lloc de tasques. Cada vegada que afegies un camp al model, tocaves totes les funcions. En aquesta lliçó això s'acaba: aprendràs què és un objecte en JavaScript, com es crea, com es llegeixen i es modifiquen les seves propietats, i faràs servir aquest coneixement per escriure per fi el backlog real de Nómada Tasques com un array d'objectes, amb la signatura neta descriureTasca(tasca, avui). És el canvi més important del curs fins ara, perquè a partir d'aquí el codi deixa de simular el model de dades i passa a contenir-lo.
Contingut
- El problema: sis arrays que cal mantenir alineats
- Què és un objecte: literals, propietats i valors
- Accés amb punt i accés amb claudàtors
- Afegir, modificar i esborrar propietats
- Propietats que no existeixen:
undefinedi l'encadenament opcional - Comprovar si una propietat existeix
- El backlog real: un array d'objectes
- La recompensa:
descriureTasca(tasca, avui) - Objectes imbricats: la tasca amb les seves subtasques
- Noms de propietat computats
- Recórrer un objecte:
for...iniObject.keys/values/entries - Objectes com a diccionari de consulta
- Una menció breu a
MapiSet - Errors Habituals i Consells
- Exercicis
- Conclusió
- El problema: sis arrays que cal mantenir alineats
Abans de celebrar la solució, mira el problema amb tota la seva cruesa. Així estava el backlog al final del Mòdul 3:
const titols = ['Redissenyar la sala polivalent', 'Cartelleria del taller de serigrafia', /* ... */];
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'];Aquest disseny té tres defectes greus, i val la pena anomenar-los:
| Defecte | Què passa a la pràctica |
|---|---|
| Fragilitat | Si esborres la tasca 3 de titols i t'oblides d'esborrar-la de hores, a partir d'aquell índex tot el backlog està desalineat i el programa no dóna cap error: simplement menteix |
| Signatures enormes | descriureTasca(id, titol, responsable, prioritat, estat, horesEstimades, dataLimit, avui) — vuit paràmetres posicionals que cal recordar en ordre |
| No hi ha "la tasca" | No existeix cap valor al programa que sigui una tasca. Només existeix el número 3, i l'acord tàcit que l'índex 3 significa el mateix en sis llocs |
Un objecte resol els tres alhora: agrupa dades relacionades sota un mateix valor, amb noms en lloc de posicions.
- Què és un objecte: literals, propietats i valors
Un objecte és una col·lecció de parells clau → valor. La manera de crear-lo és el literal d'objecte: claus {} amb les parelles a dins.
const tasca = {
id: 1,
titol: 'Redissenyar la sala polivalent',
responsable: 'Iván',
prioritat: 'alta',
estat: 'en-curs',
etiquetes: ['espai', 'disseny'],
horesEstimades: 12,
dataLimit: '2026-09-30',
revisor: 'Marta'
};Vocabulari precís, perquè el faràs servir a tot el mòdul:
- Propietat: cada parella completa, per exemple
titol: 'Redissenyar la sala polivalent'. - Clau (o nom de propietat): la part esquerra,
titol. Sempre és un string internament (o unSymbol, que no veurem aquí). Encara que escriguis{ 1: 'un' }, la clau real és'1'. - Valor: la part dreta. Pot ser qualsevol cosa: un número, un string,
null, un array (etiquetes), un altre objecte, o fins i tot una funció (això són els mètodes, i arriben a la lliçó següent).
Tres detalls de sintaxi que estalvien disgustos:
const exemple = {
titol: 'Sense cometes si és un identificador vàlid',
'data-limit': 'Amb cometes si porta guions, espais o comença per dígit',
estat: 'pendent', // la coma final (trailing comma) està permesa
};Les claus no porten cometes si són identificadors vàlids de JavaScript (lletres, dígits que no van al principi, _ i $). Si la clau té un guió, un espai o un accent problemàtic, cal posar-la entre cometes. A Nómada Tasques fem servir sempre claus en camelCase sense cometes: horesEstimades, dataLimit.
I recorda el que ja saps de 01-05: typeof d'un objecte és 'object', i els objectes són valors de referència. Dos objectes amb el mateix contingut no són iguals:
console.log(typeof tasca); // 'object'
console.log({ id: 1 } === { id: 1 }); // false ← són dos objectes diferents
- Accés amb punt i accés amb claudàtors
Hi ha dues maneres de llegir una propietat, i no són intercanviables.
// Notació de punt: la clau s'escriu literalment al codi
console.log(tasca.titol); // 'Redissenyar la sala polivalent'
console.log(tasca.horesEstimades); // 12
// Notació de claudàtors: la clau és una EXPRESSIÓ que s'avalua a un string
console.log(tasca['titol']); // 'Redissenyar la sala polivalent'
const camp = 'responsable';
console.log(tasca[camp]); // 'Iván' ← impossible amb el punt
console.log(tasca.camp); // undefined ← busca la clau literal "camp"Aquesta última línia és l'error clàssic: tasca.camp busca una propietat anomenada camp, no el contingut de la variable camp. Quan cal cada notació:
| Situació | Notació | Exemple |
|---|---|---|
| Saps el nom en escriure el codi | Punt (preferida: més curta i llegible) | tasca.estat |
| El nom és dins d'una variable | Claudàtors | tasca[camp] |
| El nom es calcula | Claudàtors | tasca['hores' + 'Estimades'] |
| El nom no és un identificador vàlid | Claudàtors | config['mode-fosc'] |
| El nom és un número | Claudàtors | pesos[3] |
Un cas pràctic on els claudàtors són imprescindibles: una funció genèrica que ordena o compara pel camp que li diguis.
function valorDelCamp(tasca, camp) {
return tasca[camp]; // amb punt seria sempre la propietat "camp"
}
console.log(valorDelCamp(tasca, 'prioritat')); // 'alta'
console.log(valorDelCamp(tasca, 'revisor')); // 'Marta'
- Afegir, modificar i esborrar propietats
Els objectes declarats amb const sí que es poden modificar. El que const impedeix és reassignar la variable, no canviar el contingut de l'objecte al qual apunta (això ho vas veure a 01-05 amb primitius davant de referències).
const tasca = { id: 1, titol: 'Redissenyar la sala polivalent', estat: 'pendent' };
// Modificar: la clau ja existeix
tasca.estat = 'en-curs';
// Afegir: la clau no existia, es crea sobre la marxa
tasca.responsable = 'Iván';
tasca['horesEstimades'] = 12;
console.log(tasca);
// { id: 1, titol: 'Redissenyar la sala polivalent', estat: 'en-curs',
// responsable: 'Iván', horesEstimades: 12 }
// Esborrar: l'operador delete elimina la propietat sencera
delete tasca.horesEstimades;
console.log(tasca.horesEstimades); // undefined
// Reassignar la variable SÍ que falla
// tasca = { id: 2 }; // ✗ TypeError: Assignment to constant variableSobre delete, dos matisos importants:
deleteno és el mateix que posarundefined. Després dedelete tasca.revisor, la claurevisorja no existeix a l'objecte. Després detasca.revisor = undefined, la clau existeix i el seu valor ésundefined. Llegint amb el punt no notes la diferència, però'revisor' in tascasí (apartat 6), i també ho notenObject.keysiJSON.stringify(lliçó 04-08).- A Nómada Tasques gairebé mai fem servir
delete. El model diu querevisorés «string onull»: un revisor absent es representa ambnull, que és un valor explícit, no esborrant la clau. Esborrar camps del model canònic trenca les funcions que esperen trobar-los.
- Propietats que no existeixen:
undefined i l'encadenament opcional
undefined i l'encadenament opcionalLlegir una propietat inexistent no dóna error: retorna undefined.
const tasca = { id: 1, titol: 'Redissenyar la sala polivalent' };
console.log(tasca.responsable); // undefined ← no existeix, però no falla
console.log(tasca.titlo); // undefined ← una errada també retorna undefined!Aquesta segona línia explica per què les errades en noms de propietat són tan traïdores: JavaScript no t'avisa, simplement et dóna undefined i la fallada apareix tres funcions més enllà. (A 08-02 veuràs com un linter i les anotacions de tipus detecten això abans d'executar.)
El que sí que peta és intentar llegir una propietat de undefined:
const tasca = { id: 1, titol: 'Redissenyar la sala polivalent' };
console.log(tasca.revisio.autor);
// ✗ TypeError: Cannot read properties of undefined (reading 'autor')Aquí és on torna l'encadenament opcional ?. que vas conèixer a 01-06. Si el que hi ha a l'esquerra és null o undefined, l'expressió sencera s'atura i retorna undefined en lloc de llançar l'error:
console.log(tasca.revisio?.autor); // undefined ← sense TypeError
console.log(tasca.revisio?.autor ?? 'sense revisar'); // 'sense revisar'Combinat amb ?? (fusió de nuls), tens la parella perfecta per llegir dades que potser no hi són: ?. evita l'error i ?? posa el valor per defecte. Un exemple directe del projecte, on revisor pot ser null:
function nomDelRevisor(tasca) {
return tasca.revisor ?? 'pendent per assignar';
}
console.log(nomDelRevisor({ revisor: 'Marta' })); // 'Marta'
console.log(nomDelRevisor({ revisor: null })); // 'pendent per assignar'Compte amb abusar de
?.. Si escriustasca?.titol?.lengtha tot arreu, estàs tapant el fet que no saps quines dades reps. Fes-lo servir on la dada és legítimament opcional (revisor, un camp que encara no existeix), no com a xarxa de seguretat universal.
- Comprovar si una propietat existeix
Hi ha quatre maneres de preguntar «aquest objecte té aquesta propietat?», i no responen exactament a la mateixa pregunta.
| Forma | Retorna true quan… |
Amb { revisor: undefined } |
Amb propietats heretades |
|---|---|---|---|
tasca.revisor !== undefined |
El valor no és undefined |
false |
Sí que les veu |
'revisor' in tasca |
La clau existeix, amb el valor que sigui | true |
Sí que les veu |
tasca.hasOwnProperty('revisor') |
La clau és pròpia de l'objecte | true |
No |
Object.hasOwn(tasca, 'revisor') |
La clau és pròpia (versió moderna, recomanada) | true |
No |
const tasca = { id: 1, titol: 'Pressupost de la fusteria', revisor: undefined };
console.log(tasca.revisor !== undefined); // false ← el valor és undefined
console.log('revisor' in tasca); // true ← però la clau sí que hi és
console.log(Object.hasOwn(tasca, 'revisor')); // true
console.log(Object.hasOwn(tasca, 'prioritat')); // falseQuina fer servir? La regla pràctica:
Object.hasOwn(obj, clau)quan vols saber si la clau hi és present. És la forma moderna i la que hauries d'escriure per defecte.obj.clau !== undefined(o directamentif (obj.clau), amb compte amb els falsy de 01-07) quan el que t'importa és el valor.infunciona, però també troba propietats heretades del prototip (Mòdul 5), cosa que de vegades sorprèn.hasOwnPropertyés el veterà;Object.hasOwnexisteix precisament per substituir-lo amb menys paranys.
Exemple real: validar que un objecte rebut té tots els camps obligatoris del model.
const CAMPS_OBLIGATORIS = ['id', 'titol', 'responsable', 'prioritat',
'estat', 'etiquetes', 'horesEstimades', 'dataLimit'];
function campsQueFalten(tasca) {
const falten = [];
for (const camp of CAMPS_OBLIGATORIS) {
if (!Object.hasOwn(tasca, camp)) falten.push(camp);
}
return falten;
}
console.log(campsQueFalten({ id: 7, titol: 'Revisar la guillotina' }));
// [ 'responsable', 'prioritat', 'estat', 'etiquetes', 'horesEstimades', 'dataLimit' ]Fixa't que revisor no és a la llista: és opcional, i la seva absència es representa amb null.
- El backlog real: un array d'objectes
Ha arribat el moment. Aquest bloc de codi és la font de veritat de la resta del mòdul; el veuràs reaparèixer a les set lliçons següents.
'use strict';
const AVUI = '2026-09-20';
const backlog = [
{
id: 1,
titol: 'Redissenyar la sala polivalent',
responsable: 'Iván',
prioritat: 'alta',
estat: 'en-curs',
etiquetes: ['espai', 'disseny'],
horesEstimades: 12,
dataLimit: '2026-09-30',
revisor: 'Marta'
},
{
id: 2,
titol: 'Cartelleria del taller de serigrafia',
responsable: 'Marta',
prioritat: 'mitjana',
estat: 'pendent',
etiquetes: ['serigrafia', 'comunicació'],
horesEstimades: 6,
dataLimit: '2026-10-15',
revisor: null
},
{
id: 3,
titol: 'Actualitzar el web de reserves',
responsable: 'Lucía',
prioritat: 'alta',
estat: 'pendent',
etiquetes: ['web', 'reserves'],
horesEstimades: 14,
dataLimit: '2026-10-02',
revisor: 'Iván'
},
{
id: 4,
titol: 'Inventari de tintes de serigrafia',
responsable: 'Marta',
prioritat: 'baixa',
estat: 'feta',
etiquetes: ['serigrafia', 'magatzem'],
horesEstimades: 3,
dataLimit: '2026-09-12',
revisor: null
},
{
id: 5,
titol: 'Guia sobre enquadernació per a residents',
responsable: 'Iván',
prioritat: 'mitjana',
estat: 'en-curs',
etiquetes: ['enquadernació', 'documentació'],
horesEstimades: 8,
dataLimit: '2026-11-05',
revisor: 'Lucía'
},
{
id: 6,
titol: 'Pressupost de la fusteria',
responsable: 'Iván',
prioritat: 'alta',
estat: 'pendent',
etiquetes: ['fusteria', 'compres'],
horesEstimades: 5,
dataLimit: '2026-09-05',
revisor: 'Marta'
}
];Les dades són exactament les mateixes de sempre —48 h totals, 45 h obertes, l'Iván amb tres tasques i 25 h, «Pressupost de la fusteria» vençuda— però ara cada tasca és una unitat. Comprova-ho:
console.log(backlog.length); // 6
console.log(backlog[0].titol); // 'Redissenyar la sala polivalent'
console.log(backlog[5].dataLimit); // '2026-09-05'
console.log(backlog[2].etiquetes[0]); // 'web'Llegeix a poc a poc backlog[2].etiquetes[0]: de l'array backlog agafa l'element de la posició 2 (un objecte), d'aquell objecte llegeix la propietat etiquetes (un array) i d'aquell array agafa l'element 0 (un string). L'accés s'encadena d'esquerra a dreta, pas a pas.
flowchart TD
A["backlog<br/>(array de 6 elements)"] --> B0["[0] objecte tasca"]
A --> B1["[1] objecte tasca"]
A --> B2["[2] objecte tasca"]
A --> B5["[5] objecte tasca"]
B2 --> C1["id: 3"]
B2 --> C2["titol: 'Actualitzar el web de reserves'"]
B2 --> C3["etiquetes: ['web', 'reserves']"]
B2 --> C4["revisor: 'Iván'"]
I ara la comprovació que importa: esborrar una tasca ja no pot desalinear res.
// Amb arrays paral·lels calia esborrar l'índex 3 en SIS arrays.
// Ara la tasca sencera desapareix d'un cop, i res no es descol·loca.
- La recompensa:
descriureTasca(tasca, avui)
descriureTasca(tasca, avui)Recuperem les funcions del Mòdul 3 i reescrivim-les amb el model real.
function pesDePrioritat(prioritat) {
if (prioritat === 'alta') return 3;
if (prioritat === 'mitjana') return 2;
if (prioritat === 'baixa') return 1;
return 0;
}
// R10: vençuda = data límit passada i la tasca no està acabada.
function estaVencuda(tasca, avui) {
return tasca.dataLimit < avui && tasca.estat !== 'feta';
}
function marcaEstat(estat) {
if (estat === 'feta') return '✓';
if (estat === 'en-curs') return '▸';
return '○';
}
function descriureTasca(tasca, avui) {
const marca = marcaEstat(tasca.estat);
const avis = estaVencuda(tasca, avui) ? ' ⚠ VENÇUDA' : '';
const revisio = tasca.revisor ?? 'sense revisor';
return `${marca} [${tasca.id}] ${tasca.titol} · ${tasca.responsable} · ` +
`${tasca.prioritat} · ${tasca.horesEstimades} h · rev: ${revisio}${avis}`;
}
console.log(descriureTasca(backlog[0], AVUI));
// ▸ [1] Redissenyar la sala polivalent · Iván · alta · 12 h · rev: Marta
console.log(descriureTasca(backlog[5], AVUI));
// ○ [6] Pressupost de la fusteria · Iván · alta · 5 h · rev: Marta ⚠ VENÇUDA
console.log(descriureTasca(backlog[3], AVUI));
// ✓ [4] Inventari de tintes de serigrafia · Marta · baixa · 3 h · sense revisorCompara les dues signatures de descriureTasca:
| Mòdul 3 | Ara | |
|---|---|---|
| Signatura | descriureTasca(id, titol, responsable, prioritat, estat, horesEstimades, dataLimit, avui) |
descriureTasca(tasca, avui) |
| Crida | descriureTasca(ids[i], titols[i], responsables[i], prioritats[i], estats[i], hores[i], datesLimit[i], AVUI) |
descriureTasca(backlog[i], AVUI) |
Afegir el camp revisor |
Canviar la signatura i totes les crides | No canvia res: el camp ja viatja a dins |
| Risc d'equivocar l'ordre | Alt: set strings i números seguits | Cap |
Aquest és el benefici real dels objectes: les dades que van juntes viatgen juntes. I fixa't en l'efecte col·lateral a estaVencuda(tasca, avui): va passar de tres paràmetres a dos, i ja no pot rebre la data d'una tasca amb l'estat d'una altra.
- Objectes imbricats: la tasca amb les seves subtasques
A 03-07 vas fer servir, de mala gana, un objecte amb subtasques imbricades. Ara ja el pots llegir amb propietat:
const tascaRedisseny = {
id: 1,
titol: 'Redissenyar la sala polivalent',
responsable: 'Iván',
estat: 'en-curs',
horesEstimades: 0,
subtasques: [
{ id: 11, titol: 'Mesurar i aixecar el plànol', responsable: 'Iván',
estat: 'feta', horesEstimades: 3, subtasques: [] },
{ id: 12, titol: 'Triar el mobiliari', responsable: 'Marta',
estat: 'en-curs', horesEstimades: 0,
subtasques: [
{ id: 121, titol: 'Demanar pressupostos', responsable: 'Marta',
estat: 'feta', horesEstimades: 2, subtasques: [] },
{ id: 122, titol: 'Visitar dos proveïdors', responsable: 'Marta',
estat: 'pendent', horesEstimades: 2, subtasques: [] }
]
},
{ id: 13, titol: 'Pintar i muntar', responsable: 'Iván',
estat: 'pendent', horesEstimades: 5, subtasques: [] }
]
};
console.log(tascaRedisseny.subtasques[1].subtasques[0].titol); // 'Demanar pressupostos'
console.log(tascaRedisseny.subtasques[0].subtasques.length); // 0
console.log(tascaRedisseny.subtasques[9]?.titol); // undefined (sense error)L'última línia mostra per què ?. és tan útil amb estructures imbricades: subtasques[9] no existeix, i sense ?. obtindries un TypeError. Un valor es pot imbricar tan profundament com vulguis: un objecte dins d'un array dins d'un objecte dins d'un array. L'accés sempre es llegeix d'esquerra a dreta, un pas cada vegada.
- Noms de propietat computats
De vegades la clau d'una propietat no es coneix fins que el programa s'executa. La sintaxi de nom de propietat computat permet calcular-la dins del mateix literal, posant-la entre claudàtors:
const camp = 'estat';
const valor = 'en-curs';
const filtre = { [camp]: valor };
console.log(filtre); // { estat: 'en-curs' }
// Sense claudàtors, la clau seria literalment "camp"
const malFiltre = { camp: valor };
console.log(malFiltre); // { camp: 'en-curs' } ✗Es pot fer servir qualsevol expressió que produeixi un string:
const persona = 'Iván';
const comptador = {
[`tasques_de_${persona}`]: 3,
[`hores_de_${persona}`]: 25
};
console.log(comptador); // { tasques_de_Iván: 3, hores_de_Iván: 25 }Un ús pràctic: una funció que construeix un criteri de cerca a partir del camp que l'usuari triï al futur panell de Nómada Tasques.
function criteri(camp, valor) {
return { [camp]: valor };
}
console.log(criteri('responsable', 'Lucía')); // { responsable: 'Lucía' }
console.log(criteri('prioritat', 'alta')); // { prioritat: 'alta' }
- Recórrer un objecte:
for...in i Object.keys/values/entries
for...in i Object.keys/values/entriesUn array es recorre per posicions; un objecte es recorre per claus. Hi ha dos camins.
for...in recorre les claus (sempre com a strings):
const tasca = backlog[0];
for (const clau in tasca) {
console.log(`${clau}: ${tasca[clau]}`);
}
// id: 1
// titol: Redissenyar la sala polivalent
// responsable: Iván
// prioritat: alta
// estat: en-curs
// etiquetes: espai,disseny
// horesEstimades: 12
// dataLimit: 2026-09-30
// revisor: MartaFixa't en l'accés: dins del bucle cal fer servir claudàtors, tasca[clau], perquè la clau és dins d'una variable. tasca.clau donaria undefined nou vegades.
Els tres mètodes d'Object converteixen l'objecte en arrays, que ja saps manejar:
console.log(Object.keys(tasca));
// [ 'id', 'titol', 'responsable', 'prioritat', 'estat',
// 'etiquetes', 'horesEstimades', 'dataLimit', 'revisor' ]
console.log(Object.values(tasca));
// [ 1, 'Redissenyar la sala polivalent', 'Iván', 'alta', 'en-curs',
// [ 'espai', 'disseny' ], 12, '2026-09-30', 'Marta' ]
console.log(Object.entries(tasca));
// [ [ 'id', 1 ], [ 'titol', 'Redissenyar la sala polivalent' ], ... ]
// ↑ un array de parells [clau, valor]Comparació pràctica:
| Forma | Què et dóna | Avantatge | Inconvenient |
|---|---|---|---|
for...in |
Una clau per volta | Sintaxi directa | També recorre propietats heretades del prototip (Mòdul 5) |
Object.keys(o) |
Array de claus pròpies | Només pròpies; pots fer servir mètodes d'array | Crea un array intermedi |
Object.values(o) |
Array de valors propis | Ideal per sumar o comptar | Perds els noms |
Object.entries(o) |
Array de parells [clau, valor] |
Ho tens tot | Sintaxi una mica més densa (millora molt amb desestructuració, 04-06) |
A la pràctica, Object.entries amb un for...of és l'opció per defecte, i for...in queda per a casos puntuals:
A 04-06 veuràs com escriure això mateix com a for (const [clau, valor] of Object.entries(tasca)), que és molt més llegible.
Un exemple útil: comptar quants camps del model estan buits.
function campsBuits(tasca) {
const buits = [];
for (const clau of Object.keys(tasca)) {
const valor = tasca[clau];
if (valor === null || valor === undefined || valor === '') buits.push(clau);
}
return buits;
}
console.log(campsBuits(backlog[1])); // [ 'revisor' ]
console.log(campsBuits(backlog[0])); // []
- Objectes com a diccionari de consulta
Hi ha un segon ús dels objectes, diferent d'«agrupar els camps d'una entitat»: fer-los servir com a diccionari o taula de consulta, on les claus són dades i no noms fixos. Ja ho vas veure de passada a 02-03 com a alternativa al switch:
const PESOS = { alta: 3, mitjana: 2, baixa: 1 };
function pesDePrioritat(prioritat) {
return PESOS[prioritat] ?? 0; // claudàtors: la clau ve en una variable
}
console.log(pesDePrioritat('alta')); // 3
console.log(pesDePrioritat('urgent')); // 0 ← clau desconeguda → undefined → ?? 0Compara les dues versions de la mateixa funció:
Cadena d'if (03-01) |
Diccionari | |
|---|---|---|
| Afegir una prioritat nova | Escriure un altre if |
Afegir una línia a PESOS |
| Les dades són… | Barrejades amb la lògica | Separades, en un sol lloc |
| Cost de la cerca | Recorre els if en ordre |
Accés directe per clau |
El patró es generalitza a qualsevol traducció valor → valor:
const MARQUES = { feta: '✓', 'en-curs': '▸', pendent: '○' };
const NOMS_ESTAT = { pendent: 'Pendent', 'en-curs': 'En curs', feta: 'Acabada' };
function marcaEstat(estat) {
return MARQUES[estat] ?? '?';
}
function nomEstat(estat) {
return NOMS_ESTAT[estat] ?? 'Desconegut';
}
console.log(`${marcaEstat('en-curs')} ${nomEstat('en-curs')}`); // ▸ En cursObserva que 'en-curs' necessita cometes com a clau, perquè el guió no és vàlid en un identificador. En llegir-la, MARQUES['en-curs'] amb claudàtors; MARQUES.en-curs seria un error de sintaxi (JavaScript ho llegiria com una resta).
I un tercer ús, l'índex per identificador, que evita recórrer el backlog sencer cada vegada que busques una tasca:
const perId = {};
for (const tasca of backlog) {
perId[tasca.id] = tasca; // la clau numèrica es converteix en el string '1', '2'...
}
console.log(perId[3].titol); // 'Actualitzar el web de reserves'
console.log(perId[99]?.titol); // undefinedA 04-05 construiràs aquest índex de manera molt més elegant amb reduce.
- Una menció breu a
Map i Set
Map i SetEls objectes com a diccionari tenen dos límits: les seves claus només poden ser strings (tota la resta es converteix a string) i no porten el compte de quantes entrades tenen. Per a aquests casos JavaScript ofereix dues estructures dedicades:
Map: un diccionari on la clau pot ser qualsevol valor (un número de debò, un objecte, una funció), conserva l'ordre d'inserció i té.size.Set: una col·lecció de valors sense duplicats, ideal per desduplicar etiquetes.
const etiquetesUniques = new Set(['serigrafia', 'compres', 'serigrafia']);
console.log(etiquetesUniques.size); // 2No les desenvolupem aquí: apareixen amb detall a Cercar, Ordenar i Agregar Dades, quan hagis d'agrupar i desduplicar de debò. Per al 95 % del que faràs en aquest curs, un objecte literal és suficient.
Errors Habituals i Consells
1. Fer servir el punt quan la clau és dins d'una variable.
const camp = 'estat';
console.log(tasca.camp); // ✗ undefined
console.log(tasca[camp]); // ✓ 'en-curs'2. Oblidar les cometes en llegir una clau amb claudàtors.
console.log(tasca[titol]); // ✗ ReferenceError: titol is not defined
console.log(tasca['titol']); // ✓Amb claudàtors, el que hi ha a dins és una expressió. Sense cometes, JavaScript busca una variable amb aquest nom.
3. Creure que const congela l'objecte. No ho fa: const protegeix la variable, no el contingut. Per impedir canvis a l'objecte existeix Object.freeze, que veuràs a 04-08.
4. Confondre «no existeix» amb «val undefined». Fes servir Object.hasOwn quan la pregunta sigui sobre la clau; compara amb undefined quan sigui sobre el valor.
5. Encadenar accessos sense protecció. tasca.subtasques[0].titol peta si subtasques està buit. Amb tasca.subtasques[0]?.titol obtens undefined i segueixes viu.
6. Posar una coma de més entre propietats. { id: 1,, titol: 'x' } és un error de sintaxi. La coma final ({ id: 1, }) sí que està permesa i és bona pràctica: els diffs de Git surten més nets.
Consell professional. Tria un ordre fix per als camps —a Nómada Tasques: id, titol, responsable, prioritat, estat, etiquetes, horesEstimades, dataLimit, revisor— i respecta'l a tots els objectes. Llegir codi és comparar, i comparar és molt més ràpid quan tot està al mateix lloc.
Exercicis
Exercici 1 — Fitxes del taller. Crea un objecte taller que descrigui l'espai de Taller Nómada amb les propietats nom ('Taller Nómada'), places (18), zones (array amb 'coworking', 'serigrafia', 'enquadernació', 'fusteria') i coordinadora ('Marta'). Després:
- Mostra per consola la segona zona.
- Afegeix la propietat
obertDissabtesamb valortrue. - Canvia
placesa 20. - Esborra
coordinadorai comprova ambObject.hasOwnque ja no hi és. - Llegeix una propietat inexistent
telefonsense que el programa falli, mostrant'sense telèfon'si no existeix.
Exercici 2 — Diccionari de terminis. Escriu una funció diesDeMarge(prioritat) que, fent servir un objecte de consulta (ni if ni switch), retorni quants dies de marge té una tasca segons la seva prioritat: alta → 3, mitjana → 7, baixa → 15. Si la prioritat és desconeguda, ha de retornar 30. Prova-ho amb 'alta', 'baixa' i 'cap'.
Exercici 3 — Inspector de tasques. Escriu una funció inspeccionar(tasca) que rebi una tasca del backlog i retorni un string multilínia amb una línia per camp, en el format clau: valor, saltant-se el camp etiquetes. Després escriu resumDeCamps(tasca), que retorni un objecte amb tres propietats: total (nombre de camps), buits (nombre de camps el valor dels quals sigui null) i noms (array amb els noms dels camps). Prova-ho amb backlog[1].
Solucions
Exercici 1
const taller = {
nom: 'Taller Nómada',
places: 18,
zones: ['coworking', 'serigrafia', 'enquadernació', 'fusteria'],
coordinadora: 'Marta'
};
// 1. Segona zona: l'índex 1, perquè els arrays comencen a 0
console.log(taller.zones[1]); // 'serigrafia'
// 2. Afegir una propietat nova
taller.obertDissabtes = true;
// 3. Modificar-ne una d'existent
taller.places = 20;
// 4. Esborrar i comprovar
delete taller.coordinadora;
console.log(Object.hasOwn(taller, 'coordinadora')); // false
// 5. Propietat inexistent sense fallar
console.log(taller.telefon ?? 'sense telèfon'); // 'sense telèfon'
console.log(taller);
// { nom: 'Taller Nómada', places: 20,
// zones: [ 'coworking', 'serigrafia', 'enquadernació', 'fusteria' ],
// obertDissabtes: true }L'error freqüent aquí és el punt 1: taller.zones[2] retornaria 'enquadernació', la tercera. Recorda que la posició 1 és el segon element.
Exercici 2
const MARGE_EN_DIES = { alta: 3, mitjana: 7, baixa: 15 };
function diesDeMarge(prioritat) {
return MARGE_EN_DIES[prioritat] ?? 30;
}
console.log(diesDeMarge('alta')); // 3
console.log(diesDeMarge('baixa')); // 15
console.log(diesDeMarge('cap')); // 30Detall important: aquí ?? és més correcte que ||. Si algun dia una prioritat tingués marge 0, MARGE_EN_DIES[p] || 30 retornaria 30 perquè 0 és falsy, mentre que ?? només actua davant de null o undefined. És exactament la distinció que vas estudiar a 01-06.
Exercici 3
function inspeccionar(tasca) {
const linies = [];
for (const clau of Object.keys(tasca)) {
if (clau === 'etiquetes') continue;
linies.push(`${clau}: ${tasca[clau]}`);
}
return linies.join('\n');
}
function resumDeCamps(tasca) {
const noms = Object.keys(tasca);
let buits = 0;
for (const clau of noms) {
if (tasca[clau] === null) buits++;
}
return { total: noms.length, buits: buits, noms: noms };
}
console.log(inspeccionar(backlog[1]));
// id: 2
// titol: Cartelleria del taller de serigrafia
// responsable: Marta
// prioritat: mitjana
// estat: pendent
// horesEstimades: 6
// dataLimit: 2026-10-15
// revisor: null
console.log(resumDeCamps(backlog[1]));
// { total: 9, buits: 1,
// noms: [ 'id', 'titol', 'responsable', 'prioritat', 'estat',
// 'etiquetes', 'horesEstimades', 'dataLimit', 'revisor' ] }Dues observacions. Primera: resumDeCamps retorna un objecte, que és just el que recomanava 03-03 per retornar diversos valors amb nom; ara ja saps com es construeix aquest objecte. Segona: el continue dins del for...of és el mateix de 02-04, i aquí serveix per saltar-se el camp etiquetes sense trencar el bucle.
Conclusió
Has saldat el deute. Un objecte és una col·lecció de parells clau → valor que s'escriu amb un literal {}; les seves propietats es llegeixen amb punt quan en coneixes el nom i amb claudàtors quan el nom és dins d'una variable o es calcula. Saps afegir i modificar propietats sobre la marxa, esborrar-les amb delete (i per què en aquest projecte gairebé mai no convé), i què passa en llegir-ne una que no existeix: undefined, sense error, tret que provis de seguir encadenant —per a això hi ha ?., que ara entens de debò—. Tens també la taula de les quatre maneres de comprovar l'existència d'una clau, amb Object.hasOwn com a opció recomanada.
I sobretot tens el backlog real: sis objectes dins d'un array, amb les mateixes dades de sempre (48 h totals, 45 obertes, l'Iván amb 25 h, la fusteria vençuda) però per fi estructurades. Amb ell has reescrit estaVencuda(tasca, avui) i descriureTasca(tasca, avui), i has comprovat el canvi d'escala: de vuit paràmetres posicionals a dos, de sis arrays que calia mantenir sincronitzats a una llista que s'esborra i s'ordena sense por. Has vist a més els objectes imbricats de l'arbre de subtasques, els noms de propietat computats { [clau]: valor }, les quatre maneres de recórrer un objecte (for...in, Object.keys, Object.values, Object.entries) i l'ús dels objectes com a diccionari de consulta, que converteix quatre if en una línia.
Però un objecte pot contenir alguna cosa més que dades. Si PESOS pot viure dins d'un objecte, per què no pesDePrioritat? I si el tauler de Taller Nómada guardés, al costat de la seva llista de tasques, les mateixes operacions d'afegir una tasca o canviar-li l'estat? Això és exactament el que veurem a Mètodes d'Objecte i la Paraula Clau this: funcions desades en propietats, la sintaxi abreujada per escriure-les i aquella paraula clau, this, que és alhora la més útil i la més mal entesa de JavaScript. De passada tancaràs un altre deute pendent del Mòdul 3: per què les funcions fletxa es comporten de manera diferent de les normals.
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
