Hoppa till innehåll

Utmaningar

Nedan finns sex frågor om att lagra data i webbläsaren. Frågorna bygger på veckans artiklar Lagring i webbläsaren och Web Storage. Den sista frågan bygger på IndexedDB, som är en orientering.

Varje fråga har ett antal alternativ att välja bland. Klicka på “Visa lösning” för att se svaret och förklaringen.

Om du är osäker kan du alltid testa i webbläsarens konsol själv eller ta upp frågan på en handledning.

En applikation ska komma ihåg om användaren har valt mörkt eller ljust tema, också nästa gång användaren besöker sidan. Valet behövs bara av JavaScript-koden som byter tema. Var passar det bäst att spara det?

  1. I en cookie.
  2. I localStorage.
  3. I sessionStorage.
  4. I IndexedDB.

Svaret är alternativ 2. Ett litet värde som bara koden på sidan behöver, och som ska ligga kvar, är precis vad localStorage är till för.

En cookie skulle skickas med i varje förfrågan till servern, som inte har någon nytta av den. sessionStorage försvinner när fliken stängs, så valet skulle vara borta nästa besök. IndexedDB skulle fungera, men är ett asynkront och betydligt krångligare API för ett enda värde.

Vilka av de här sidorna delar på samma localStorage?

  1. http://localhost:5173/ och http://localhost:5173/settings
  2. http://localhost:5173/ och http://localhost:4173/
  3. http://localhost:5173/ och http://127.0.0.1:5173/
  4. https://example.com/ och http://example.com/

Svaret är alternativ 1. Lagringen delas per origin, alltså schema, värdnamn och port. Sökvägen ingår inte, så alla sidor på http://localhost:5173 delar på samma lagring.

I alternativ 2 skiljer porten, i alternativ 3 värdnamnet och i alternativ 4 schemat. Det är tre olika origins med var sin lagring, även om två av dem pekar på samma dator och två på samma webbplats.

localStorage.setItem('quiz:time', 42)
const time = localStorage.getItem('quiz:time')
console.log(time + 8)
  1. 50
  2. '428'
  3. NaN
  4. Ett TypeError kastas, eftersom setItem() bara tar emot strängar.

Svaret är alternativ 2. Web Storage lagrar bara strängar, och setItem() gör om talet till strängen '42' utan att säga något. getItem() ger tillbaka strängen, och '42' + 8 slår ihop strängarna till '428'. Gör om värdet till ett tal när du läser det: Number(localStorage.getItem('quiz:time')).

const scores = JSON.parse(localStorage.getItem('quiz:scores'))
scores.push({ nickname: 'Ada', time: 31.4 })

Koden körs första gången, när inget har sparats under quiz:scores. Vad händer?

  1. scores blir en tom array, och resultatet läggs till.
  2. JSON.parse() kastar ett SyntaxError.
  3. scores blir null, och scores.push() kastar ett TypeError.
  4. getItem() kastar ett fel, eftersom nyckeln inte finns.

Svaret är alternativ 3. getItem() ger null när nyckeln saknas, utan fel. JSON.parse(null) ger också null, eftersom null görs om till strängen 'null', som är giltig JSON. Felet kommer först på nästa rad, när push() anropas på null.

Ge ett standardvärde, och kontrollera gärna att det sparade verkligen är en array:

const saved = JSON.parse(localStorage.getItem('quiz:scores'))
const scores = Array.isArray(saved) ? saved : []

Kan det sparade vara ogiltig JSON, till exempel efter att någon ändrat det i utvecklarverktygen, behövs också try och catch runt JSON.parse().

En applikation består av komponenterna quiz-app, som orkestrerar, och leaderboard, som visar topplistan. Var hör koden som läser och skriver topplistan i localStorage hemma?

  1. I leaderboard, eftersom det är den som visar topplistan.
  2. I quiz-app, eller i en modul som quiz-app använder. leaderboard får listan via en metod.
  3. I quiz-app, som skickar listan till leaderboard som JSON i ett attribut.
  4. Både quiz-app och leaderboard läser själva från localStorage, så att de alltid har samma lista.

Svaret är alternativ 2. Att spara och läsa är ett beslut om applikationen, och det fattas av den som orkestrerar. leaderboard visar den lista den får, och går då att använda med en lista från en server, från en fil eller från ett test.

Alternativ 1 och 4 gör leaderboard beroende av en viss nyckel i localStorage, och i alternativ 4 finns kunskapen om lagringen dessutom på två ställen. Alternativ 3 skickar en array som en sträng i ett attribut. Attribut är strängar, och en array skickas med en metod, som showScores().

Den här frågan bygger på artikeln om IndexedDB, som är en orientering.

const store = database.transaction('scores', 'readwrite').objectStore('scores')
const response = await fetch('https://api.example.com/flags/se')
const flag = await response.blob()
store.add({ nickname: 'Ada', time: 31.4, flag })

Vad händer på sista raden?

  1. Posten sparas, eftersom transaktionen väntar tills add() anropas.
  2. Posten sparas, men utan flag, som inte går att lagra i IndexedDB.
  3. Ett TransactionInactiveError kastas, eftersom transaktionen redan har avslutats.
  4. Koden väntar tills föregående transaktion är klar och sparar sedan posten.

Svaret är alternativ 3. En transaktion avslutas av sig själv när den inte har några pågående anrop och koden lämnar tillbaka kontrollen till händelseloopen. Det händer vid await fetch(), och när add() till sist anropas finns ingen aktiv transaktion.

Hämta allt du behöver först, och skapa transaktionen när du direkt kan använda den. Alternativ 2 stämmer inte heller: IndexedDB kan lagra en Blob, till skillnad från Web Storage.

CCBY