`Array`-objekt

Denne artikel forklarer om Array-objektet.

Jeg vil forklare praktiske anvendelser af arrays trin for trin på en letforståelig måde.

YouTube Video

Array-objekt

JavaScripts Array-objekt er en af de vigtigste strukturer, der danner grundlaget for al slags databehandling. Fra grundlæggende array-operationer til højere ordens funktioner, der er nyttige for effektiv datatransformation, er der mange funktioner, du bør kende.

Grundlæggende om arrays

I JavaScript er arrays en grundlæggende datastruktur til håndtering af flere værdier sammen. Her introducerer vi, hvordan du opretter arrays, samt hvordan du læser og skriver deres elementer med enkle eksempler.

 1// Create arrays in different ways
 2const arr1 = [1, 2, 3];           // array literal
 3const arr2 = new Array(4, 5, 6);  // Array constructor
 4const arr3 = Array.of(7, 8, 9);   // Array.of
 5
 6console.log("arr1 created with literal.   :", arr1);
 7console.log("arr2 created with constructor:", arr2);
 8console.log("arr3 created with Array.of.  :", arr3);
 9// arr1 created with literal.   : [ 1, 2, 3 ]
10// arr2 created with constructor: [ 4, 5, 6 ]
11// arr3 created with Array.of.  : [ 7, 8, 9 ]
12
13// Access and modify elements
14let first = arr1[0];              // read element
15console.log("First element of arr1:", first);
16// First element of arr1: 1
17
18arr1[1] = 20;                     // modify element
19console.log("arr1 after modifying index 1:", arr1);
20// arr1 after modifying index 1: [ 1, 20, 3 ]
21
22const len = arr1.length;          // get length
23console.log("Length of arr1:", len);
24// Length of arr1: 3
  • Denne kode viser tre måder at oprette arrays på, hvordan man læser og opdaterer elementer ved hjælp af indekser, og hvordan man får længden ved hjælp af length-egenskaben.
  • Array-litteraler er de mest almindelige og læselige, og bruges oftest i dagligdags situationer.

Tilføjelse og fjernelse af elementer (i slutningen eller starten)

Arrays gør det nemt at tilføje eller fjerne elementer i slutningen eller starten. Disse operationer er også nyttige, når man implementerer strukturer som stabler eller køer.

 1// Push and pop (stack-like)
 2const stack = [];
 3console.log("Initial stack:", stack);
 4
 5stack.push(1);                    // push 1
 6console.log("After push(1):", stack);
 7
 8stack.push(2);                    // push 2
 9console.log("After push(2):", stack);
10
11const last = stack.pop();         // pop -> 2
12console.log("Popped value:", last);
13console.log("Stack after pop():", stack);
14
15// Unshift and shift (queue-like)
16const queue = [];
17console.log("Initial queue:", queue);
18
19queue.push('a');                  // add to end
20console.log("After push('a'):", queue);
21
22queue.unshift('start');           // add to front
23console.log("After unshift('start'):", queue);
24
25const firstItem = queue.shift();  // remove from front
26console.log("Shifted value:", firstItem);
27console.log("Queue after shift():", queue);
28
29// Initial stack: []
30// After push(1): [ 1 ]
31// After push(2): [ 1, 2 ]
32// Popped value: 2
33// Stack after pop(): [ 1 ]
34
35// Initial queue: []
36// After push('a'): [ 'a' ]
37// After unshift('start'): [ 'start', 'a' ]
38// Shifted value: start
39// Queue after shift(): [ 'a' ]
  • push og pop opererer i slutningen af et array. De er ideelle til implementering af stak-strukturer.
  • unshift og shift opererer i begyndelsen af et array. Vær dog opmærksom på, at operationer i starten kræver, at alle elementers indekser forskydes internt, hvilket gør det til en dyr operation.

Håndtering af elementer i midten (splice og slice)

Når du håndterer elementer i midten af et array, skal du vælge mellem splice og slice afhængigt af, om du vil ændre det originale array eller ej. Hvis du blot vil udtrække en del af et array, skal du bruge slice; hvis du vil ændre selve arrayet, f.eks. ved at indsætte eller slette elementer, skal du bruge splice.

 1// slice (non-destructive)
 2const nums = [0, 1, 2, 3, 4];
 3console.log("Original nums:", nums);
 4
 5const part = nums.slice(1, 4); // returns [1, 2, 3]
 6console.log("Result of nums.slice(1, 4):", part);
 7console.log("nums after slice (unchanged):", nums);
 8
 9// splice (destructive)
10const arr = [10, 20, 30, 40];
11console.log("\nOriginal arr:", arr);
12
13// remove 1 item at index 2, insert 25 and 27
14arr.splice(2, 1, 25, 27);
15console.log("After arr.splice(2, 1, 25, 27):", arr);
16
17// Original nums: [ 0, 1, 2, 3, 4 ]
18// Result of nums.slice(1, 4): [ 1, 2, 3 ]
19// nums after slice (unchanged): [ 0, 1, 2, 3, 4 ]
20
21// Original arr: [ 10, 20, 30, 40 ]
22// After arr.splice(2, 1, 25, 27): [ 10, 20, 25, 27, 40 ]
  • slice udtrækker kun elementer og ændrer ikke det originale array.
  • splice tilføjer eller fjerner elementer og ændrer selve array'et, så vær især opmærksom på dens indflydelse på adfærden.

Iteration (for / for...of / forEach)

Der er flere måder at bearbejde arrays sekventielt på, og du kan vælge efter formål og kodestilpræference. Her er tre typiske loop-konstruktioner.

 1const items = ['apple', 'banana', 'cherry'];
 2console.log("Items:", items);
 3
 4// classic for
 5console.log("\n--- Classic for loop ---");
 6for (let i = 0; i < items.length; i++) {
 7  console.log(`Index: ${i}, Value: ${items[i]}`);
 8}
 9
10// for...of (recommended for values)
11console.log("\n--- for...of loop ---");
12for (const item of items) {
13  console.log(`Value: ${item}`);
14}
15
16// forEach (functional style)
17console.log("\n--- forEach loop ---");
18items.forEach((item, index) => {
19  console.log(`Index: ${index}, Value: ${item}`);
20});
  • for-løkken er den mest fleksible, da den tillader indeksoperationer og fin kontrol over iterationen med break og andre udsagn.
  • for...of giver en kortfattet måde at håndtere elementværdier på og er mest balanceret med hensyn til læsbarhed.
  • forEach gør det muligt at skrive kode i funktionel stil og er velegnet til operationer med bivirkninger, som logning eller opdatering af data for hvert element. Bemærk dog, at du ikke kan bruge break eller continue, og at det ikke er egnet til asynkron behandling med await.

map / filter / reduce — Højere ordens funktioner

map, filter og reduce er højereordensfunktioner, der ofte bruges ved transformation, filtrering eller aggregering af arrays. Da du tydeligt kan udtrykke gentagne processer, bliver din kode enkel og let at forstå.

 1const numbers = [1, 2, 3, 4, 5];
 2console.log("Original numbers:", numbers);
 3
 4// map: transform each item
 5const doubled = numbers.map(n => n * 2);
 6console.log("\nResult of map (n * 2):", doubled);
 7
 8// filter: select items
 9const evens = numbers.filter(n => n % 2 === 0);
10console.log("Result of filter (even numbers):", evens);
11
12// reduce: accumulate to single value
13const sum = numbers.reduce((acc, n) => acc + n, 0);
14console.log("Result of reduce (sum):", sum);
15
16// Original numbers: [ 1, 2, 3, 4, 5 ]
17// Result of map (n * 2): [ 2, 4, 6, 8, 10 ]
18// Result of filter (even numbers): [ 2, 4 ]
19// Result of reduce (sum): 15
  • Disse metoder gør det muligt at fokusere på, hvad du vil gøre på en deklarativ måde, hvilket forbedrer læsbarheden og mindsker uønskede bivirkninger.

find / findIndex / some / every

Her er et overblik over søge- og betingelsestjek-metoder. Disse er nyttige til at finde elementer, der opfylder bestemte betingelser, eller til at udføre booleske tjek på sættet.

 1const users = [
 2  { id: 1, name: 'Alice' },
 3  { id: 2, name: 'Bob' },
 4  { id: 3, name: 'Carol' }
 5];
 6
 7console.log("Users:", users);
 8
 9// Find the first user whose name is 'Bob'
10const bob = users.find(user => user.name === 'Bob');
11console.log("\nResult of find (name === 'Bob'):", bob);
12
13// Find index of the user whose id is 3
14const indexOfId3 = users.findIndex(user => user.id === 3);
15console.log("Result of findIndex (id === 3):", indexOfId3);
16
17// Check if there exists a user with id = 2
18const hasId2 = users.some(user => user.id === 2);
19console.log("Result of some (id === 2):", hasId2);
20
21// Check if all users have a numeric id
22const allHaveNumericId = users.every(user => typeof user.id === 'number');
23console.log("Result of every (id is number):", allHaveNumericId);
24// Result of find (name === 'Bob'): { id: 2, name: 'Bob' }
25// Result of findIndex (id === 3): 2
26// Result of some (id === 2): true
27// Result of every (id is number): true
  • find returnerer det første element, der opfylder betingelsen.
  • findIndex returnerer indekset for det element, der opfylder betingelsen.
  • some returnerer true, hvis der er mindst ét element, der opfylder betingelsen.
  • every returnerer true, hvis alle elementer opfylder betingelsen.

Disse metoder er meget nyttige i array-behandling, så brug dem passende for situationen for at holde koden kort og klar.

Sortering og sammenligningsfunktioner

Arrays sorteres med sort, men som standard sammenlignes elementer som strenge, hvilket kan føre til uventede resultater ved sortering af tal.

 1const nums = [10, 2, 33, 4];
 2console.log("Original nums:", nums);
 3
 4// Default sort: compares elements as strings (not suitable for numbers)
 5nums.sort();
 6console.log("\nAfter default sort (string comparison):", nums);
 7
 8// Numeric ascending sort using a compare function
 9nums.sort((a, b) => a - b);
10console.log("After numeric sort (a - b):", nums);
11
12// Sort objects by a property
13const people = [{ age: 30 }, { age: 20 }, { age: 25 }];
14console.log("\nOriginal people:", people);
15
16people.sort((a, b) => a.age - b.age);
17console.log("After sorting people by age:", people);
18
19// After default sort (string comparison): [ 10, 2, 33, 4 ]
20// After numeric sort (a - b): [ 2, 4, 10, 33 ]
21// Original people: [ { age: 30 }, { age: 20 }, { age: 25 } ]
22// After sorting people by age: [ { age: 20 }, { age: 25 }, { age: 30 } ]
  • Når du sorterer tal eller objekter, skal du altid angive en sammenligningsfunktion for at sortere dem i den ønskede rækkefølge.
  • I en sammenligningsfunktion placerer en negativ returværdi a før b, en positiv placerer b før a, og 0 bevarer deres rækkefølge.

Array-kopiering og uforanderlighed

Når du kopierer arrays, er det vigtigt at forstå forskellen mellem 'referencekopi' og 'shallow copy' (overfladisk kopi). Vær især opmærksom på, at hvis der er objekter inde i array'et, vil en flad kopi få de indre objekter til at blive delt.

Referencekopi

Når du tildeler et array til en anden variabel, bliver selve arrayet ikke duplikeret; i stedet kopieres referencen, der peger på det samme array.

 1const a = [1, 2, 3];
 2console.log("Original a:", a);
 3
 4// Reference copy; modifying b also affects a
 5const b = a;
 6console.log("\nReference copy b = a:", b);
 7// Reference copy b = a: [ 1, 2, 3 ]
 8
 9b[0] = 100;
10console.log("After modifying b[0] = 100:");
11console.log("a:", a);  // a: [ 100, 2, 3 ] (affected)
12console.log("b:", b);  // b: [ 100, 2, 3 ]
  • Ved en referencekopi vil ændringer i arrayet via den kopierede variabel også blive afspejlet i den oprindelige variabel, der refererer til det samme array.

Overfladisk kopi (shallow copy)

Ved at bruge slice() eller spread-syntaksen oprettes en 'overfladisk kopi', fordi kun værdierne duplikeres; det oprindelige og det kopierede array behandles som separate enheder.

 1const a = [1, 2, 3];
 2console.log("Original a:", a);
 3
 4// Shallow copy (new array)
 5const b = a.slice();
 6console.log("\nShallow copy using slice():", b);
 7// Shallow copy using slice(): [ 100, 2, 3 ]
 8
 9const c = [...a];
10console.log("Shallow copy using spread [...a]:", c);
11// Shallow copy using spread [...a]: [ 100, 2, 3 ]
12
13// Modifying c or d does NOT affect a
14b[1] = 200;
15c[2] = 300;
16console.log("\nAfter modifying b[1] = 200 and c[2] = 300:");
17console.log("a:", a);   // [ 100, 2, 3 ]
18console.log("b:", b);   // [ 100, 200, 3 ]
19console.log("c:", c);   // [ 100, 2, 300 ]
  • Denne kode demonstrerer, at en overfladisk kopi af et array, oprettet med slice() eller spread-syntaksen, ikke påvirker det oprindelige array.

Overfladisk kopi og uforanderlighed

Selv hvis du duplikerer et array med en 'overfladisk kopi', kan der opstå utilsigtet deling, hvis arrayet indeholder objekter.

 1// Shallow copy doesn't clone inner objects
 2const nested = [{ x: 1 }, { x: 2 }];
 3console.log("\nOriginal nested:", nested);
 4// Original nested: [ { x: 1 }, { x: 2 } ]
 5
 6const shallow = nested.slice();
 7console.log("Shallow copy of nested:", shallow);
 8// Shallow copy of nested: [ { x: 1 }, { x: 2 } ]
 9
10// Changing inner object affects both arrays
11shallow[0].x = 99;
12console.log("\nAfter shallow[0].x = 99:");
13console.log("nested:", nested);
14console.log("shallow:", shallow);
15// nested: [ { x: 99 }, { x: 2 } ]
16// shallow: [ { x: 99 }, { x: 2 } ]
  • Denne kode viser, at ved overfladisk kopi deles de indre objekter, så ændringer på disse påvirker både det oprindelige array og kopien.
  • Hvis du har brug for uafhængige data, skal du lave en 'dyb kopi' såsom ved at bruge structuredClone() eller JSON-konvertering.

Nyttige hjælpefunktioner

Følgende er hjælpefunktioner, der ofte bruges, når man arbejder med arrays. Ved at bruge dem hensigtsmæssigt til dit formål kan du skrive kort og læsbar kode.

includes

includes-metoden kontrollerer, om en bestemt værdi findes i et array.

1// includes: check if array contains a value
2const letters = ['a', 'b', 'c'];
3const hasB = letters.includes('b');
4console.log("letters:", letters);  // [ 'a', 'b', 'c' ]
5console.log("letters.includes('b'):", hasB); // true
  • I denne kode bruges includes-metoden til let at afgøre, om en bestemt værdi findes i arrayet.

concat

concat-metoden returnerer et nyt array, hvor det angivne array eller værdier tilføjes til slutningen, mens det oprindelige array forbliver uændret.

1// concat: merge arrays without mutation
2const a1 = [1, 2];
3const a2 = [3, 4];
4const combined = a1.concat(a2);
5console.log("a1.concat(a2):", combined); // [ 1, 2, 3, 4 ]
6console.log("a1(unchanged):", a1);       // [ 1, 2 ]
  • Denne kode viser, at concat ikke er destruktiv, så du kan generere et nyt array, mens det oprindelige bevares.

flat

Med flat-metoden kan du udflade sammenkædede (næstede) arrays.

1// flat and flatMap: flatten arrays or map + flatten in one step
2const nested = [1, [2, [3]]];
3console.log("nested:", nested); // nested: [ 1, [ 2, [ 3 ] ] ]
4console.log("nested.flat():", nested.flat()); // default depth = 1
5// nested.flat(): [ 1, 2, [ 3 ] ]
  • Denne kode demonstrerer resultatet af at udflade et array med ét niveau.
  • flat gør det muligt at angive dybden, så du fleksibelt kan udflade arrays efter behov.

flatMap

flatMap-metoden anvender en transformation på hvert element og udflader derefter automatisk resultaterne til et endimensionelt array.

1// flat and flatMap: flatten arrays or map + flatten in one step
2const words = ['hello world', 'hi'];
3console.log("words:", words); // [ 'hello world', 'hi' ]
4
5const splitWords = words.flatMap(w => w.split(' '));
6console.log("words.flatMap(w => w.split(' ')):", splitWords);
7// words.flatMap(w => w.split(' ')): [ 'hello', 'world', 'hi' ]
  • Denne kode viser et eksempel, hvor hver streng i et array opdeles ved mellemrum, og resultaterne samles og udflades til ét enkelt array.

join

join-metoden opretter en streng ved at kombinere elementerne i et array med en angivet separator.

1// join: combine elements into a string with a separator
2const csv = ['a', 'b', 'c'].join(',');
3console.log("['a', 'b', 'c'].join(','):", csv);  // a,b,c
  • I denne kode bruges join-metoden til at konvertere et array til en kommasepareret streng.

Almindelige faldgruber

Array-operationer kan virke enkle ved første øjekast, men der er flere ting, som let kan føre til utilsigtet adfærd. Mange faldgruber er lette at overse under daglige array-operationer, så hvis du husker på følgende punkter, vil din kodes pålidelighed blive betydeligt forbedret.

  • Array's sort() sorterer som standard ved strengsammenligning. Når du sorterer tal korrekt, skal du altid angive en sammenligningsfunktion.
  • Kopiering af arrays (via slice eller spread-syntaks m.m.) opretter en flad kopi. Hvis dine arrays indeholder objekter, så vær forsigtig, fordi de oprindelige data kan blive ændret utilsigtet.
  • splice er en destruktiv metode, der ændrer array'et direkte, mens slice er en ikke-destruktiv metode, der ikke ændrer det oprindelige array. Det er vigtigt at bruge dem rigtigt efter dine behov.
  • forEach er ikke egnet til løkker med asynkron behandling ved brug af await. Hvis du vil udføre asynkrone operationer i rækkefølge pålideligt, anbefales det at bruge for...of.

Praktisk eksempel

Nedenfor er et eksempel, der kombinerer array-metoder for at 'få den samlede alder fra brugerdata, udtrække dem der er 30 år eller ældre og lave en liste over navne.'.

 1const users = [
 2  { name: 'Alice', age: 28 },
 3  { name: 'Bob', age: 34 },
 4  { name: 'Carol', age: 41 },
 5  { name: 'Dave', age: 19 }
 6];
 7
 8console.log("Users:", users);
 9
10// sum ages
11const totalAge = users.reduce((acc, u) => acc + u.age, 0);
12console.log("\nTotal age of all users:", totalAge);
13// Total age of all users: 122
14
15// filter and map names of users 30 and older
16const namesOver30 = users
17  .filter(u => u.age >= 30)
18  .map(u => u.name);
19
20console.log("Users aged 30 or older (names):", namesOver30);
21// Users aged 30 or older (names): [ 'Bob', 'Carol' ]
  • Ved at kæde reduce, filter og map sammen kan du nemt skrive aggregering, betinget udtrækning og transformation af data.
  • En sådan 'databehandlingspipeline' er meget læsbar og bruges ofte i virkelige applikationer, da det er en stil med få bivirkninger.

Sammendrag

Med JavaScript-arrays er selv grundlæggende operationer meget alsidige, og ved at bruge højere ordens funktioner bliver din kode mere kortfattet og udtryksfuld. Der er mange ting at forstå, men når du først mestrer at bruge hver af dem korrekt, vil databehandlingen blive meget lettere.

Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.

YouTube Video