Detta blogginlägg handlar om referens- och primitiva datatyper och hur de lagras i datorns minne med ett exempel i React. Inlägget är producerat av Leo Kelmendi, en av Provide ITs tech leads med över 7 års erfarenhet inom webb- och systemutveckling. Du kan läsa originalartikeln här.

JavaScript och några andra programmeringsspråk använder två olika sorter av RAM-minne som kallas Stack och Heap.

JavaScripts exekveringscykel

Som du kan se i bilden ovan är stack och heap två olika minnessektorer (memory sectors) i RAM-minnet. Dessa används av JavaScript i olika scenarion beroende på vilken datatyp som bearbetas eller är efterfrågad från event loopen.

Stack

Stack används vanligtvis för exekvering av trådar (thread execution). Detta betyder att när nästa funktion i programflödet blir anropad, kommer ett block på toppen av stacken reserveras för funktionens lokala variabler och en del annan metadata. När funktionen returnerar, frigörs blocket upp och kan återanvändas i nästa funktionsanrop.

Stack är i grunden en lättillgänglig datastruktur, som helt enkelt hanterar sina föremål som en hög (stack). Det är bara föremål vars storlek som är känt från början som kan användas i denna hög, detta gäller Number, String och Boolean.

Heap

Heap är en datastruktur för föremål vars storlek och struktur inte kan konkretiseras från början. Eftersom föremål och matriser (arrays) kan bli modifierade och förändrade under programmets exkavering måste de placeras i heapen. Heap-minnet kan ses som en sorterad hög jämfört med stacken.

För varje heap-föremål förvaras den exakta adressen i en minnespekare (pointer) som visar föremålet i heap-minnet, eftersom det är svårare att allokera i heapen. Minnespekaren sin tur är förvarad i stack-minnet.

Vad har Stack och Heap att göra med referens- och primitiva datatyper?

Primitiva datatyper (primitive data types) är lagrade i stacken eftersom storleken kan förutbestämmas. Medan referensdatatyper (reference data types) lagras i heapen eftersom storleken inte kan bestämmas i förtid. Minnespekarna lagras i stacken till vart datan finns i heapen.

Primitiva dataytyper

Primitiva datatyper (primitive data types) är förenklat när datans storlek och struktur är känd sedan tidigare. Exempel på primitiva datatyper i JavaScript är:

  • Null
  • Undefined
  • Boolean
  • Number
  • String

Referansdatatyper

Referensdatatyper (reference data types) är data när storleken och strukturen är dynamisk och inte kan bestämmas från början, eller till och med är okänd. I JavaScript är referensdatatyper till exempel:

  • Array
  • Object
  • Function

Men varför har detta någon betydelse för utvecklare?

Faktumet att det endast är referensdatatypernas minnespekare (pointers) som lagras i stacken har stor betydelse. Detta leder ofta till oförutsägbart och konstigt beteende vid användning av referensdatatyper.

När kan oförutsägbara beteenden ske?

Om vi tar ett exempel med React-komponenten nedan:

Vi ser här en varukorgs-komponent som renderar föremål och hanterar borttagning av föremål ut programmets state (application state).

Men om vi tittar mer ingående på deleteCartItemHandler i koden, så kan vi se brister i logiken baserat på vad vi diskuterade tidigare gällande objekt (Object) och matriser (Arrays) som referenstyper i JavaScript.

Detta betyder att vi hämtar föremål från programlagringen (state) och lagrar det i en variabel (const items). Mer exakt betyder detta att vi lagrar en minnespekare från stacken som i sin tur pekar på orginalobjektet i heapen.

Trots att detta fungerar och inte kastar några fel, så är detta inte hur referenstypen borde manipuleras. Detta beror på att det enkelt kan leda till oförutsägbara applikationer, vilket är ansett som en dålig praxis.

Ett bättre förhållningssätt skulle vara att kopiera referensen och sen lagra ändringarna, eller så kallat oföränderligt sätt (immutable way) att hantera referenstyper i JavaScript:

Den enda förändringen som gjordes är när vi hämtade information från föremål i varukorgen, så använde vi JavaScripts spread operator […object]. Detta är bättre eftersom spread operator faktiskt kopierar alla värden från den angivna referensen direkt från heap-minnet, istället för att bara kopiera minnespekaren (pointern) från stacken. Detta är ansett vara ett bättre sätt att förändra datan i heapen på ett oföränderligt sätt (immutable way).

Detta är de JavaScript metoder som kan användas för att kopiera referensvärden direkt från heapen:

  • […object] – spread operator
  • [data].slice() — slice metoden
  • Object.assign({}, obj) — kopia av objekt
  • JSON.parse(JSON.stringify(arr/ob)) — djup klon av objekt (objekten måste kunna serialiseras som JSON)

Dock är det viktigt att komma ihåg att vissa av dessa metoder kommer skapa en kopia av ett objekt och inte en djup kloning (deep clone).