Fas servir arrays des del Mòdul 1, però sempre com una llista de la qual es treuen coses per índex dins d'un for. Això és aprofitar potser el deu per cent del que un array sap fer. En aquesta lliçó estudies l'estructura a fons: les cinc maneres de crear-ne un, què és exactament length i què passa si el canvies, per què typeof [] === 'object' i què fer servir en lloc seu, i sobretot la distinció que més errors evita en JavaScript real: mètodes que modifiquen l'array original davant de mètodes que en retornen un de nou. Aquella taula que construiràs a l'apartat 7 és material de consulta permanent. Tot el recorregut es recolza en dos usos concrets del projecte: mantenir la llista de tasques del tauler de Taller Nómada i gestionar les etiquetes d'una tasca, aquell camp que el model canònic defineix com a «array de strings» i que fins ara amb prou feines has tocat.

Contingut

  1. Què és un array i què no
  2. Cinc maneres de crear un array
  3. Índexs, at() i els índexs negatius
  4. length: llegir-lo, i què passa en assignar-lo
  5. Forats: els sparse arrays
  6. Array.isArray i per què typeof no serveix
  7. La taula que cal tenir a mà: mutar davant de retornar
  8. Afegir i treure pels extrems: push, pop, shift, unshift
  9. splice: esborrar, inserir i reemplaçar
  10. slice: copiar un tros sense tocar l'original
  11. concat i join
  12. Cercar: indexOf, lastIndexOf, includes i el seu límit amb objectes
  13. reverse i una nota sobre les versions immutables
  14. Arrays imbricats, flat i flatMap
  15. Exemple integrat: les etiquetes d'una tasca
  16. Errors Habituals i Consells
  17. Exercicis
  18. Conclusió

  1. Què és un array i què no

Un array és una llista ordenada de valors, accessibles per la seva posició (índex), que comença a 0.

const etiquetes = ['espai', 'disseny', 'mobiliari'];

console.log(etiquetes[0]);   // 'espai'
console.log(etiquetes[2]);   // 'mobiliari'
console.log(etiquetes[3]);   // undefined  ← no existeix, però no falla

Tres propietats defineixen un array davant d'un objecte normal:

Objecte Array
Clau Un nom (titol, estat) Una posició numèrica (0, 1, 2…)
Ordre No garantit per a claus qualssevol Garantit i significatiu
Mida Es compta amb Object.keys(o).length array.length, mantingut pel motor

I un advertiment tècnic que explica moltes raresses: un array de JavaScript és, per sota, un objecte amb claus numèriques i una propietat length especial. Per això pots fer això, encara que no ho hagis de fer:

const rar = ['a', 'b'];
rar.autor = 'Lucía';         // ✗ funciona, però és una propietat, no un element
console.log(rar.length);     // 2   ← length només compta els índexs
console.log(rar);            // [ 'a', 'b', autor: 'Lucía' ]

Regla pràctica: en un array hi van elements, mai propietats amb nom. Si necessites noms, fes servir un objecte.

  1. Cinc maneres de crear un array

// 1. Literal: la forma normal, la que faràs servir el 95 % de les vegades
const etiquetes = ['espai', 'disseny'];
const buit = [];

// 2. new Array(): té un parany famós
const tres = new Array('a', 'b', 'c');   // [ 'a', 'b', 'c' ]  ← diversos arguments: elements
const sis = new Array(6);                // [ <6 empty items> ] ← UN número: longitud!
console.log(sis.length);                 // 6, però està completament buit

// 3. Array.of(): resol aquest parany, sempre crea elements
console.log(Array.of(6));                // [ 6 ]      ← un array amb el número 6
console.log(Array.of('a', 'b'));         // [ 'a', 'b' ]

// 4. Array.from(): construeix un array a partir d'alguna cosa iterable o "semblant a un array"
console.log(Array.from('serigrafia'));   // [ 's','e','r','i','g','r','a','f','i','a' ]
console.log(Array.from({ length: 4 }, (_, i) => i + 1));   // [ 1, 2, 3, 4 ]

// 5. Array(n).fill(): l'idioma per crear n posicions amb el mateix valor
const horesPerDia = Array(5).fill(0);
console.log(horesPerDia);                // [ 0, 0, 0, 0, 0 ]

Val la pena aturar-se en dos:

new Array(6) davant d'Array.of(6). Amb un únic argument numèric, new Array interpreta que li estàs demanant un array d'aquesta longitud, buit. És una incoherència històrica del llenguatge i la raó per la qual existeix Array.of. Evita new Array tret del cas deliberat de reservar longitud.

Array.from amb funció de mapatge. El segon paràmetre és una funció que s'aplica a cada posició, amb el valor i l'índex. És la manera idiomàtica de generar seqüències:

// Els propers 5 dies laborables a partir d'una data (versió simplificada amb strings)
const diesDelMes = Array.from({ length: 5 }, (_, i) => `2026-09-${20 + i}`);
console.log(diesDelMes);
// [ '2026-09-20', '2026-09-21', '2026-09-22', '2026-09-23', '2026-09-24' ]

El _ com a nom del primer paràmetre és una convenció: significa «aquest argument existeix però no el faig servir».

Compte amb fill i els objectes, perquè comparteix la referència (recorda 01-05):

const files = Array(3).fill({ hores: 0 });
files[0].hores = 8;
console.log(files);      // [ { hores: 8 }, { hores: 8 }, { hores: 8 } ]  ✗ el mateix objecte!

const correcte = Array.from({ length: 3 }, () => ({ hores: 0 }));
correcte[0].hores = 8;
console.log(correcte);   // [ { hores: 8 }, { hores: 0 }, { hores: 0 } ]  ✓

fill copia el mateix valor tres vegades; si aquest valor és una referència, les tres posicions apunten al mateix objecte. Array.from amb funció crida la funció tres vegades i crea tres objectes diferents.

  1. Índexs, at() i els índexs negatius

L'accés clàssic amb claudàtors ja el coneixes. El que potser no coneixes és at(), que fa el mateix però admet índexs negatius comptant des del final:

const etiquetes = ['espai', 'disseny', 'mobiliari', 'pressupost'];

console.log(etiquetes[0]);       // 'espai'
console.log(etiquetes.at(0));    // 'espai'

console.log(etiquetes.at(-1));   // 'pressupost'   ← l'últim
console.log(etiquetes.at(-2));   // 'mobiliari'

console.log(etiquetes[-1]);      // undefined  ✗ compte! busca una propietat anomenada "-1"

Abans d'at(), l'últim element s'obtenia amb etiquetes[etiquetes.length - 1], que funciona però repeteix el nom de l'array i és més sorollós. Quan l'array és el resultat d'una expressió llarga, la diferència es nota:

const ultimaTasca = tauler.tasques.at(-1);
// davant de
const ultimaTascaLlarga = tauler.tasques[tauler.tasques.length - 1];

  1. length: llegir-lo, i què passa en assignar-lo

length no és el nombre d'elements: és l'índex més alt utilitzat, més un. I, a diferència de gairebé qualsevol altre llenguatge, es pot escriure.

const etiquetes = ['espai', 'disseny', 'mobiliari'];
console.log(etiquetes.length);   // 3

// Assignar un length MENOR trunca l'array (destrueix elements)
etiquetes.length = 2;
console.log(etiquetes);          // [ 'espai', 'disseny' ]

// Assignar un length MAJOR crea forats
etiquetes.length = 5;
console.log(etiquetes);          // [ 'espai', 'disseny', <3 empty items> ]

// L'idioma per buidar un array sense canviar la referència
etiquetes.length = 0;
console.log(etiquetes);          // []

Aquesta última línia és un truc útil: etiquetes = [] crearia un array nou (i amb const fallaria), mentre que etiquetes.length = 0 buida el que ja existeix, de manera que qualsevol altra variable que l'estigués apuntant també veurà l'array buit. És exactament la diferència entre reassignar i mutar que vas estudiar amb les referències.

I compte amb assignar un índex molt alt:

const tasques = ['a', 'b'];
tasques[9] = 'j';
console.log(tasques.length);     // 10
console.log(tasques);            // [ 'a', 'b', <7 empty items>, 'j' ]

  1. Forats: els sparse arrays

Un array amb posicions que mai no s'han assignat s'anomena sparse (dispers o amb forats). Els forats no són undefined: són l'absència de la propietat, i alguns mètodes els salten.

const ambForat = [1, , 3];       // la coma doble deixa un forat a la posició 1

console.log(ambForat.length);          // 3
console.log(ambForat[1]);              // undefined  ← en llegir-lo sembla undefined
console.log(1 in ambForat);            // false      ← però la clau no existeix
console.log(Object.keys(ambForat));    // [ '0', '2' ]

const ambUndefined = [1, undefined, 3];
console.log(1 in ambUndefined);        // true       ← aquí la clau sí que existeix

A la pràctica: evita els forats. Apareixen gairebé sempre per accident (delete tasques[2], que esborra l'element però deixa el forat, o new Array(6)), i fan que uns mètodes els ignorin i altres no, cosa que produeix bugs difícils de veure. Si vols eliminar un element de debò, fes servir splice (apartat 9), no delete:

const tasques = ['a', 'b', 'c'];
delete tasques[1];
console.log(tasques);       // [ 'a', <1 empty item>, 'c' ]   ✗ length continua sent 3

const tasques2 = ['a', 'b', 'c'];
tasques2.splice(1, 1);
console.log(tasques2);      // [ 'a', 'c' ]                   ✓ length ara és 2

  1. Array.isArray i per què typeof no serveix

console.log(typeof []);                // 'object'   ✗ inútil
console.log(typeof {});                // 'object'
console.log(typeof null);              // 'object'   (el bug històric de 01-05)

console.log(Array.isArray([]));        // true       ✓
console.log(Array.isArray({}));        // false
console.log(Array.isArray('espai'));   // false

typeof no distingeix arrays d'objectes perquè, com hem vist, un array és un objecte. Array.isArray és la comprovació correcta, i és especialment útil validant dades del model, on etiquetes ha de ser sempre un array:

function validarEtiquetes(tasca) {
  if (!Array.isArray(tasca.etiquetes)) {
    throw new Error(`Les etiquetes de la tasca ${tasca.id} han de ser un array.`);
  }
  return true;
}

validarEtiquetes({ id: 1, etiquetes: ['espai'] });   // true
// validarEtiquetes({ id: 8, etiquetes: 'espai' });  // ✗ Error

  1. La taula que cal tenir a mà: mutar davant de retornar

Aquesta és la secció més important de la lliçó. Els mètodes d'array es divideixen en dues famílies, i confondre-les és la causa número u de bugs amb llistes:

  • Els que muten: modifiquen l'array sobre el qual es criden. Solen retornar una altra cosa (el nou length, l'element eliminat…) o el mateix array.
  • Els que en retornen un de nou: deixen l'original intacte i produeixen un array (o un valor) diferent.
Mètode Muta l'original? Què retorna
push(...v) El nou length
pop() L'element eliminat
shift() El primer element, eliminat
unshift(...v) El nou length
splice(i, n, ...v) Un array amb els elements eliminats
reverse() El mateix array, ja invertit
sort(cmp) (lliçó 04-05) El mateix array, ja ordenat
fill(v) El mateix array
slice(i, j) No Un array nou amb el tros
concat(...) No Un array nou amb tot unit
join(sep) No Un string
indexOf / lastIndexOf / includes No Un número / un booleà
at(i) No L'element
flat() / flatMap(f) No Un array nou
map / filter (04-04, 04-05) No Un array nou
toSorted / toReversed (04-05) No Un array nou

Una regla mnemotècnica força fiable: els mètodes que muten tenen noms de verb d'acció sobre la llista (empènyer, treure, empalmar, invertir, ordenar, emplenar); els que retornen alguna cosa nova tenen noms de produir (llescar, concatenar, unir, aplanar, mapejar). I una regla d'estil: a Nómada Tasques preferim els mètodes que no muten sempre que sigui possible, perquè un array que ningú no modifica per sorpresa és un array del qual et pots fiar. A 04-07 veuràs com construir actualitzacions immutables completes.

flowchart TD
    A["const original = ['a','b','c']"] --> B{"Quin mètode?"}
    B -->|"original.push('d')"| C["original = ['a','b','c','d']<br/>retorna 4"]
    B -->|"original.slice(0, 2)"| D["original CONTINUA ['a','b','c']<br/>retorna ['a','b']"]

  1. Afegir i treure pels extrems: push, pop, shift, unshift

Tots quatre muten. Es distingeixen per l'extrem i la direcció:

Mètode Extrem Acció Retorna
push(v) Final Afegeix Nou length
pop() Final Treu L'element tret
unshift(v) Principi Afegeix Nou length
shift() Principi Treu L'element tret
const etiquetes = ['espai', 'disseny'];

console.log(etiquetes.push('mobiliari'));       // 3   ← retorna el length, no l'array
console.log(etiquetes);                         // [ 'espai', 'disseny', 'mobiliari' ]

console.log(etiquetes.push('pressupost', 'obra'));   // 5  ← admet diversos de cop

const ultima = etiquetes.pop();
console.log(ultima);                            // 'obra'
console.log(etiquetes.length);                  // 4

etiquetes.unshift('urgent');
console.log(etiquetes);
// [ 'urgent', 'espai', 'disseny', 'mobiliari', 'pressupost' ]

const primera = etiquetes.shift();
console.log(primera);                           // 'urgent'

Un avís de rendiment que importarà al Mòdul 9: push i pop treballen al final i són molt ràpids. shift i unshift treballen al principi i obliguen el motor a reindexar tots els elements, així que en llistes grans són notablement més costosos. Amb les sis tasques de Taller Nómada tant és; amb cent mil registres, no.

Aplicat al tauler:

const tauler = { tasques: [] };

function afegirTasca(tauler, tasca) {
  tauler.tasques.push(tasca);
  return tauler.tasques.length;
}

console.log(afegirTasca(tauler, { id: 1, titol: 'Redissenyar la sala polivalent' }));       // 1
console.log(afegirTasca(tauler, { id: 2, titol: 'Cartelleria del taller de serigrafia' })); // 2

  1. splice: esborrar, inserir i reemplaçar

splice és el mètode més versàtil i el més fàcil d'escriure malament. La seva signatura és:

array.splice(indexInici, quantsEsborrar, ...elementsAInserir)

Els seus tres usos, amb el mateix array de partida:

// ÚS 1 — ESBORRAR: segon argument > 0, sense elements a inserir
const a = ['espai', 'disseny', 'mobiliari', 'pressupost'];
const esborrats = a.splice(1, 2);         // des de l'índex 1, esborra 2
console.log(esborrats);                   // [ 'disseny', 'mobiliari' ]  ← el que s'ha eliminat
console.log(a);                           // [ 'espai', 'pressupost' ]

// ÚS 2 — INSERIR: segon argument 0, més els elements nous
const b = ['espai', 'pressupost'];
b.splice(1, 0, 'disseny', 'mobiliari');   // a l'índex 1, esborra 0, insereix 2
console.log(b);                           // [ 'espai', 'disseny', 'mobiliari', 'pressupost' ]

// ÚS 3 — REEMPLAÇAR: esborrar i inserir alhora
const c = ['espai', 'disseny', 'mobiliari'];
c.splice(1, 1, 'interiorisme');           // a l'índex 1, esborra 1, insereix 1
console.log(c);                           // [ 'espai', 'interiorisme', 'mobiliari' ]

Detalls que eviten sorpreses:

  • Retorna el que s'ha eliminat, no l'array resultant. Si escrius const nou = a.splice(1, 2), nou conté els elements esborrats. L'array modificat continua sent a.
  • Si ometes el segon argument, esborra fins al final: a.splice(2) elimina tot des de l'índex 2.
  • Admet índexs negatius: a.splice(-1, 1) esborra l'últim element.

Aplicat al projecte, eliminar una tasca del tauler pel seu id:

function eliminarTasca(tauler, id) {
  for (let i = 0; i < tauler.tasques.length; i++) {
    if (tauler.tasques[i].id === id) {
      const [eliminada] = tauler.tasques.splice(i, 1);
      return eliminada;
    }
  }
  return null;
}

(La línia const [eliminada] = ... és desestructuració d'arrays: agafa el primer element de l'array que retorna splice. És el tema de 04-06; de moment llegeix-la com «el primer del que s'ha esborrat».)

  1. slice: copiar un tros sense tocar l'original

slice(inici, final) retorna un array nou amb els elements des d'inici fins a final, sense incloure final. No toca l'original.

const tasques = ['T1', 'T2', 'T3', 'T4', 'T5', 'T6'];

console.log(tasques.slice(0, 3));    // [ 'T1', 'T2', 'T3' ]   ← el 3 NO hi entra
console.log(tasques.slice(3));       // [ 'T4', 'T5', 'T6' ]   ← des del 3 fins al final
console.log(tasques.slice(-2));      // [ 'T5', 'T6' ]         ← els dos últims
console.log(tasques.slice());        // còpia completa
console.log(tasques);                // intacte ✓

slice() sense arguments és l'idioma clàssic per copiar un array, i és el que permet ordenar sense destruir l'original:

const copia = tasques.slice();
copia.reverse();                    // reverse muta... però només la còpia
console.log(tasques[0]);            // 'T1'  ← l'original està fora de perill

Distingir slice de splice és imprescindible. Els noms s'assemblen; el comportament és oposat:

slice(i, j) splice(i, n, ...)
Muta? No
Segon argument Índex final (exclòs) Quantitat a esborrar
Retorna El tros copiat El que s'ha eliminat
Serveix per a Copiar, paginar Esborrar, inserir, reemplaçar

Truc mnemotècnic: splice té una p de «perill» (muta).

  1. concat i join

concat uneix arrays i en retorna un de nou:

const deIvan = ['Redissenyar la sala polivalent', 'Guia sobre enquadernació per a residents'];
const deMarta = ['Cartelleria del taller de serigrafia'];

const totes = deIvan.concat(deMarta);
console.log(totes.length);      // 3
console.log(deIvan.length);     // 2  ← intacte

// Admet diversos arrays i també valors solts
console.log(deIvan.concat(deMarta, ['Pressupost de la fusteria'], 'Extra').length);   // 5

En codi modern, l'operador spread de 04-07 substitueix concat gairebé sempre ([...deIvan, ...deMarta]), però concat continua sent perfectament vàlid i apareix en molt codi existent.

join converteix un array en un string, unint els elements amb el separador que li indiquis (per defecte, una coma):

const etiquetes = ['fusteria', 'compres'];

console.log(etiquetes.join());        // 'fusteria,compres'
console.log(etiquetes.join(', '));    // 'fusteria, compres'
console.log(etiquetes.join(' · '));   // 'fusteria · compres'
console.log([].join(', '));           // ''  ← array buit: string buit

És exactament el que necessites per pintar les etiquetes d'una tasca en una línia d'informe:

function liniaAmbEtiquetes(tasca) {
  const etiquetes = tasca.etiquetes.length > 0
    ? ` #${tasca.etiquetes.join(' #')}`
    : '';
  return `[${tasca.id}] ${tasca.titol}${etiquetes}`;
}

console.log(liniaAmbEtiquetes({ id: 6, titol: 'Pressupost de la fusteria',
                                etiquetes: ['fusteria', 'compres'] }));
// [6] Pressupost de la fusteria #fusteria #compres

Compte amb null i undefined dins de l'array: join els converteix en string buit, no en 'null'.

console.log(['a', null, 'b', undefined].join('-'));   // 'a--b-'

  1. Cercar: indexOf, lastIndexOf, includes i el seu límit amb objectes

const etiquetes = ['serigrafia', 'compres', 'serigrafia', 'magatzem'];

console.log(etiquetes.indexOf('compres'));        // 1
console.log(etiquetes.indexOf('serigrafia'));     // 0   ← la PRIMERA aparició
console.log(etiquetes.lastIndexOf('serigrafia')); // 2   ← l'ÚLTIMA
console.log(etiquetes.indexOf('web'));            // -1  ← no hi és

console.log(etiquetes.includes('magatzem'));      // true
console.log(etiquetes.includes('web'));           // false

indexOf retorna -1 quan no troba res, cosa que obliga a escriure comprovacions incòmodes. includes (ES2016) retorna directament un booleà i és el que has de fer servir quan només t'interessa saber si hi és:

// Antic, encara molt vist
if (etiquetes.indexOf('compres') !== -1) { }

// Modern i llegible
if (etiquetes.includes('compres')) { }

Ara, el límit important. Tots dos comparen amb ===, així que funcionen amb primitius però no amb objectes, perquè dos objectes diferents mai no són iguals encara que tinguin el mateix contingut (01-07):

const tasques = [
  { id: 1, titol: 'Redissenyar la sala polivalent' },
  { id: 6, titol: 'Pressupost de la fusteria' }
];

console.log(tasques.includes({ id: 6, titol: 'Pressupost de la fusteria' }));   // false ✗

// Amb la MATEIXA referència sí que funciona
const referencia = tasques[1];
console.log(tasques.includes(referencia));      // true ✓
console.log(tasques.indexOf(referencia));       // 1

Per cercar objectes per contingut necessites find i findIndex, que reben una funció de comparació. Són l'arrencada de la lliçó Cercar, Ordenar i Agregar Dades.

Una última raresa que convé conèixer: includes sí que troba NaN, i indexOf no.

console.log([NaN].includes(NaN));   // true
console.log([NaN].indexOf(NaN));    // -1   ← perquè NaN !== NaN (01-07)

  1. reverse i una nota sobre les versions immutables

reverse() inverteix l'array al lloc: muta.

const ordre = ['pendent', 'en-curs', 'feta'];
const retornat = ordre.reverse();

console.log(ordre);                  // [ 'feta', 'en-curs', 'pendent' ]  ← canviat!
console.log(retornat === ordre);     // true  ← retorna el MATEIX array, no una còpia

Aquest true és el parany: com que reverse retorna l'array, sembla que n'estiguis creant un de nou, i no. Si necessites conservar l'original, copia primer:

const ordre2 = ['pendent', 'en-curs', 'feta'];
const alReves = ordre2.slice().reverse();
console.log(ordre2);     // [ 'pendent', 'en-curs', 'feta' ]  ✓ intacte
console.log(alReves);    // [ 'feta', 'en-curs', 'pendent' ]

JavaScript modern incorpora toReversed(), que fa just això sense la còpia manual. El veuràs al costat de toSorted() a 04-05, on el problema de la mutació es torna realment sagnant.

  1. Arrays imbricats, flat i flatMap

Un array pot contenir arrays. És el que passa, per exemple, si reculls les etiquetes de totes les tasques:

const etiquetesPerTasca = [
  ['espai', 'disseny'],
  ['serigrafia', 'comunicació'],
  ['web', 'reserves']
];

console.log(etiquetesPerTasca[1][0]);       // 'serigrafia'
console.log(etiquetesPerTasca.length);      // 3  ← tres arrays, no sis strings

flat() aplana un nivell; amb un argument, aplana els que li diguis:

console.log(etiquetesPerTasca.flat());
// [ 'espai', 'disseny', 'serigrafia', 'comunicació', 'web', 'reserves' ]

const profund = [1, [2, [3, [4]]]];
console.log(profund.flat());          // [ 1, 2, [ 3, [ 4 ] ] ]   ← un sol nivell
console.log(profund.flat(2));         // [ 1, 2, 3, [ 4 ] ]
console.log(profund.flat(Infinity));  // [ 1, 2, 3, 4 ]           ← tots els nivells

flatMap(f) és transformar i aplanar un nivell en una sola passada. És la drecera perfecta per a «una llista amb totes les etiquetes del backlog»:

const backlog = [
  { id: 1, etiquetes: ['espai', 'disseny'] },
  { id: 2, etiquetes: ['serigrafia', 'comunicació'] },
  { id: 4, etiquetes: [] }
];

console.log(backlog.flatMap((t) => t.etiquetes));
// [ 'espai', 'disseny', 'serigrafia', 'comunicació' ]

Fixa't que la tasca sense etiquetes simplement no hi aporta res: flatMap amb un array buit és també la manera idiomàtica de «transformar i descartar alhora». Els detalls de map com a transformació arriben a la lliçó següent.

  1. Exemple integrat: les etiquetes d'una tasca

Ajuntem-ho tot en un petit joc de funcions per gestionar el camp etiquetes, respectant la regla R9 del projecte (minúscules i sense duplicats). Totes elles retornen un array nou: no muten la tasca.

'use strict';

function normalitzar(etiqueta) {
  return etiqueta.trim().toLowerCase();
}

/** Afegeix una etiqueta si no hi era. Retorna un array NOU. */
function ambEtiqueta(etiquetes, nova) {
  const neta = normalitzar(nova);
  if (neta === '') return etiquetes.slice();
  if (etiquetes.includes(neta)) return etiquetes.slice();
  return etiquetes.concat(neta);
}

/** Treu una etiqueta si hi era. Retorna un array NOU. */
function senseEtiqueta(etiquetes, treure) {
  const neta = normalitzar(treure);
  const copia = etiquetes.slice();
  const posicio = copia.indexOf(neta);
  if (posicio !== -1) copia.splice(posicio, 1);
  return copia;
}

/** Reemplaça una etiqueta per una altra, conservant la posició. */
function reanomenarEtiqueta(etiquetes, antiga, nova) {
  const copia = etiquetes.slice();
  const posicio = copia.indexOf(normalitzar(antiga));
  if (posicio !== -1) copia.splice(posicio, 1, normalitzar(nova));
  return copia;
}

const originals = ['fusteria', 'compres'];

console.log(ambEtiqueta(originals, '  URGENT '));    // [ 'fusteria', 'compres', 'urgent' ]
console.log(ambEtiqueta(originals, 'Compres'));      // [ 'fusteria', 'compres' ]  ← ja hi era
console.log(senseEtiqueta(originals, 'compres'));    // [ 'fusteria' ]
console.log(reanomenarEtiqueta(originals, 'compres', 'Pressupostos'));
// [ 'fusteria', 'pressupostos' ]

console.log(originals);   // [ 'fusteria', 'compres' ]  ✓ intacte a les quatre crides

Aquesta última línia és l'objectiu de tot el disseny: l'array de partida mai no canvia. Cada funció retorna una versió nova, exactament igual que pesDePrioritat retornava un número sense tocar res. És la mateixa idea de «funció pura» de 03-03, ara aplicada a llistes.

I aquí una comparació entre les dues estratègies, perquè vegis el preu de cadascuna:

// MUTANT: ràpida, però qualsevol que tingués l'array veu el canvi
function afegirEtiquetaMutant(tasca, nova) {
  tasca.etiquetes.push(normalitzar(nova));
}

// IMMUTABLE: crea una còpia, però ningú no s'endú sorpreses
function afegirEtiquetaSenseMutar(tasca, nova) {
  return { ...tasca, etiquetes: ambEtiqueta(tasca.etiquetes, nova) };
}

(El { ...tasca, ... } de la segona és el spread d'objectes, que estudiaràs a 04-07. Anota-t'ho mentalment: és la peça que faltarà per completar el patró.)

Errors Habituals i Consells

1. Esperar que push retorni l'array. Retorna el nou length. const llista = tasques.push(x) deixa llista valent un número.

2. Confondre slice amb splice. slice copia i no muta; splice talla i muta. Si el resultat surt estrany, comprova quin has escrit.

3. Fer servir delete per eliminar un element. Deixa un forat i no canvia length. Fes servir splice(i, 1).

4. Cercar objectes amb includes o indexOf. Comparen per referència, no per contingut. Fes servir find/findIndex (04-05).

5. Modificar un array mentre el recorres amb índexs. Si fas splice dins d'un for ascendent, els índexs es desplacen i et saltes elements. Dues solucions: recórrer cap enrere, o construir un array nou amb filter.

const tasques = ['a', 'b', 'b', 'c'];

// ✗ Es salta la segona 'b'
for (let i = 0; i < tasques.length; i++) {
  if (tasques[i] === 'b') tasques.splice(i, 1);
}
console.log(tasques);   // [ 'a', 'b', 'c' ]

// ✓ Cap enrere, els índexs pendents no es mouen
const tasques2 = ['a', 'b', 'b', 'c'];
for (let i = tasques2.length - 1; i >= 0; i--) {
  if (tasques2[i] === 'b') tasques2.splice(i, 1);
}
console.log(tasques2);  // [ 'a', 'c' ]

6. new Array(3) quan volies [3]. Fes servir Array.of(3) o directament el literal.

7. Desar propietats amb nom en un array. tasques.total = 6 és legal i és gairebé sempre un error de disseny: fes servir un objecte { tasques: [...], total: 6 }.

Consell professional. Davant de qualsevol mètode d'array que no recordis, fes-te una sola pregunta abans d'escriure'l: muta o retorna? Si muta i no volies mutar, anteposa-hi .slice(). Aquest costum —copiar abans de modificar— t'estalviarà més hores de depuració que qualsevol altre hàbit d'aquest mòdul.

Exercicis

Exercici 1 — Cua del taller. El taller de serigrafia atén encàrrecs per ordre d'arribada. Partint de const cua = ['Cartelleria del taller', 'Samarretes del curs'];:

  1. Afegeix 'Bosses de tela' al final i mostra quants n'hi ha a la cua.
  2. Atén el primer encàrrec (treu-lo del principi) i mostra'l.
  3. Afegeix 'Reimpressió urgent' al principi, perquè la Marta l'ha prioritzada.
  4. Mostra l'últim encàrrec de la cua sense fer servir length.
  5. Mostra la cua com un string separat per ' → '.

Exercici 2 — Reordenar la llista de tasques. Escriu una funció moureTasca(tasques, origen, desti) que retorni un array nou amb la tasca de la posició origen col·locada a la posició desti, sense mutar l'original. Prova-la movent la tasca de l'índex 0 a l'índex 2 a ['T1','T2','T3','T4'], i comprova que l'array original no ha canviat.

Exercici 3 — Auditoria d'etiquetes. Donat el backlog canònic (amb els seus camps id i etiquetes), escriu:

  1. totesLesEtiquetes(backlog), que retorni un array pla amb totes les etiquetes, incloses les repetides.
  2. tasquesAmbEtiqueta(backlog, etiqueta), que retorni un array amb els id de les tasques que porten aquesta etiqueta.
  3. etiquetesValides(backlog), que retorni true només si totes les tasques tenen etiquetes com a array de debò (fes servir Array.isArray).

Solucions

Exercici 1

const cua = ['Cartelleria del taller', 'Samarretes del curs'];

// 1. push retorna el nou length
console.log(cua.push('Bosses de tela'));      // 3

// 2. shift treu del principi i retorna l'element
const ates = cua.shift();
console.log(ates);                            // 'Cartelleria del taller'

// 3. unshift afegeix al principi
cua.unshift('Reimpressió urgent');
console.log(cua);
// [ 'Reimpressió urgent', 'Samarretes del curs', 'Bosses de tela' ]

// 4. at(-1) sense recórrer a length
console.log(cua.at(-1));                      // 'Bosses de tela'

// 5. join amb separador
console.log(cua.join(' → '));
// Reimpressió urgent → Samarretes del curs → Bosses de tela

Una cua real (primer a entrar, primer a sortir) s'implementa així: push per entrar i shift per sortir. Si en lloc d'una cua volguessis una pila (últim a entrar, primer a sortir), serien push i pop.

Exercici 2

function moureTasca(tasques, origen, desti) {
  if (origen < 0 || origen >= tasques.length) {
    throw new Error(`Posició d'origen no vàlida: ${origen}`);
  }
  const copia = tasques.slice();               // 1. copiar per no mutar
  const [moguda] = copia.splice(origen, 1);    // 2. treure-la del seu lloc
  copia.splice(desti, 0, moguda);              // 3. inserir-la a la destinació
  return copia;
}

const original = ['T1', 'T2', 'T3', 'T4'];
const mogut = moureTasca(original, 0, 2);

console.log(mogut);      // [ 'T2', 'T3', 'T1', 'T4' ]
console.log(original);   // [ 'T1', 'T2', 'T3', 'T4' ]  ✓ intacte

Les tres línies del cos resumeixen la lliçó sencera: copiar amb slice, treure amb splice(i, 1) aprofitant que retorna el que s'ha eliminat, i inserir amb splice(i, 0, v). Un error habitual és oblidar el slice inicial: la funció continuaria funcionant, però mutaria la llista de qui la crida, que és just el que volíem evitar.

Exercici 3

const backlog = [
  { id: 1, etiquetes: ['espai', 'disseny'] },
  { id: 2, etiquetes: ['serigrafia', 'comunicació'] },
  { id: 3, etiquetes: ['web', 'reserves'] },
  { id: 4, etiquetes: ['serigrafia', 'magatzem'] },
  { id: 5, etiquetes: ['enquadernació', 'documentació'] },
  { id: 6, etiquetes: ['fusteria', 'compres'] }
];

function totesLesEtiquetes(backlog) {
  return backlog.flatMap((tasca) => tasca.etiquetes);
}

function tasquesAmbEtiqueta(backlog, etiqueta) {
  const ids = [];
  for (const tasca of backlog) {
    if (tasca.etiquetes.includes(etiqueta)) ids.push(tasca.id);
  }
  return ids;
}

function etiquetesValides(backlog) {
  for (const tasca of backlog) {
    if (!Array.isArray(tasca.etiquetes)) return false;
  }
  return true;
}

console.log(totesLesEtiquetes(backlog).length);           // 12
console.log(tasquesAmbEtiqueta(backlog, 'serigrafia'));   // [ 2, 4 ]
console.log(tasquesAmbEtiqueta(backlog, 'web'));          // [ 3 ]
console.log(etiquetesValides(backlog));                   // true
console.log(etiquetesValides([{ id: 9, etiquetes: 'web' }]));   // false

Dotze etiquetes en total, però només onze de diferents: 'serigrafia' apareix a les tasques 2 i 4. Desduplicar-les amb un Set és un dels exemples de 04-05. I fixa't en etiquetesValides: retorna false tan bon punt troba una fallada, sense recórrer la resta. És el patró de sortida primerenca de 02-04, i és exactament el que fa el mètode every que estudiaràs a la lliçó 04-05.

Conclusió

Ara entens l'array com a estructura, no només com «allò que es recorre amb un for». Saps crear-lo de cinc maneres i per què new Array(6) no és el mateix que Array.of(6); saps que length és escrivible i que assignar-li 0 és l'idioma per buidar una llista sense canviar-ne la referència; saps què són els forats i per què convé evitar-los; i saps que typeof [] no serveix de res, mentre que Array.isArray respon exactament el que preguntes.

Sobretot, tens la taula de mutar davant de retornar, que és la brúixola de tot treball amb llistes: push, pop, shift, unshift, splice, reverse, sort i fill modifiquen l'original; slice, concat, join, at, flat, flatMap, includes i indexOf no. Has vist splice en els seus tres usos —esborrar, inserir i reemplaçar—, has distingit slice de splice, has après que includes i indexOf comparen amb === i per això fallen amb objectes, i has aplicat tot això a les etiquetes del projecte amb un joc de funcions que mai no muten la tasca original, més les funcions moureTasca i eliminarTasca del tauler.

Queda una cosa per resoldre, i és precisament la que es repeteix en gairebé tot el codi d'aquesta lliçó: els bucles escrits a mà. tasquesAmbEtiqueta recorre amb for...of i va empenyent en un array auxiliar; etiquetesValides recorre per sortir tan bon punt troba una fallada; eliminarTasca recorre amb índexs per localitzar una posició. Recórrer una llista és tan freqüent que JavaScript ofereix sis maneres diferents de fer-ho, cadascuna amb les seves regles sobre l'índex, sobre si es pot interrompre i sobre què retorna. Quina triar en cada cas és el tema d'Iteració sobre Arrays, on a més tornaràs a trobar-te amb map, aquesta vegada amb el mètode natiu i no amb la versió casolana que vas escriure a 03-06.

Curs de JavaScript: De Principiant a Avançat

Mòdul 1: Introducció a JavaScript

Mòdul 2: Estructures de Control

Mòdul 3: Funcions

Mòdul 4: Objectes i Arrays

Mòdul 5: Objectes i Funcions Avançades

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

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

Mòdul 8: Proves i Depuració

Mòdul 9: Rendiment i Optimització

Mòdul 10: Frameworks i Llibreries de JavaScript

Mòdul 11: Projecte Final

© Copyright 2026. Tots els drets reservats