Översikt
DEnna vecka börjar du titta på objektorienterade konstruktioner i form av klasser. Information om veckans upplägg annonseras löpande i kursens Zulip-kanal.
Stödjande inspelningar
Section titled “Stödjande inspelningar”Exempel - Ordräknaren
Section titled “Exempel - Ordräknaren”Ett moment som många har svårt med i examinationsuppgift A2 är funktionen “Mode”. För att underlätta löses här ett exempel som löser samma grundläggande problem.
Exempelkoden hittar du på Gitlab.
Uppgiften
Section titled “Uppgiften”Ett förberett dataunderlag – en array med ett antal korta citat, sammanfogade till en enda lång text med join('\n') – ska analyseras: hur många gånger förekommer varje ord, och vilket eller vilka ord är vanligast? Övningen är tänkt som en mindre variant av samma problem som löses i examinationsuppgift A2 (mode), utan att vara identisk med den.
Innan kodandet börjar är det värt att lösa problemet på papper: skriva upp varje ord man stöter på, kontrollera om det redan finns uppskrivet, och antingen lägga till det eller öka en räknare. Det tankesättet – ett ords namn kopplat till ett antal – är exakt vad ett objekt (nyckel-värde-par) är till för.
Städa texten
Section titled “Städa texten”Rå text innehåller skiljetecken och radbrytningar som stör en enkel uppdelning i ord. Ett första försök tar bort punkter och kommatecken med String#replace/replaceAll (replaceAll fungerar bara i tillräckligt nya Node-versioner, vilket upptäcktes live via ett felmeddelande – ett bra exempel på att kontrollera dokumentationen och sin egen miljö innan man antar att en metod finns tillgänglig). En sträng är immutable – replace returnerar alltid en ny sträng, den ursprungliga strängen kan aldrig ändras i sig.
Efter en första körning dyker fler problem upp i utskriften: kvarvarande radbrytningstecken (\n) mellan citaten. Att byta ut radbrytningarna mot mellanslag (inte mot en tom sträng) blir den sista pusselbiten – annars klistras ord från olika citat ihop till ett enda felaktigt ord.
Dela upp i ord
Section titled “Dela upp i ord”String#split(' ') delar upp den rensade, gemenifierade (toLowerCase()) textmassan till en array av ord. (En alternativ metod, slice, undersöktes också men visade sig inte passa – den extraherar en delsträng utifrån position, inte utifrån ett sökmönster.)
Bygg en frekvenstabell
Section titled “Bygg en frekvenstabell”Ett tomt objekt skapas utanför loopen (skapas det inuti loopen istället byggs ett nytt, tomt objekt upp på varje varv – ett vanligt nybörjarmisstag). Sedan itereras ordlistan igenom med en for...of-loop, som är att föredra framför en klassisk indexbaserad for-loop när själva indexet inte behövs:
const wordFrequency = {}
for (const word of words) { if (!wordFrequency[word]) { wordFrequency[word] = 1 } else { wordFrequency[word]++ }}Att fråga efter en egenskap som inte finns på ett objekt (wordFrequency[word]) ger undefined, vilket är falsy – precis det villkoret bygger på för att avgöra om ordet redan setts eller inte.
Hitta det mest förekommande ordet
Section titled “Hitta det mest förekommande ordet”Att bara plocka ut det numeriskt största värdet räcker inte – uppgiften efterfrågar vilket/vilka ord som är vanligast, inte hur många gånger. Object.values() ger alla frekvenser som en array (för att till exempel sortera fram det högsta värdet); Object.keys() ger motsvarande lista med alla unika ord. Med maxfrekvensen känd kan Object.keys(wordFrequency) itereras igenom en gång till, och varje ord vars frekvens matchar maxvärdet samlas i en resultatarray:
const result = []for (const key of Object.keys(wordFrequency)) { if (wordFrequency[key] === maxFrequency) { result.push(key) }}En detalj värd att lägga märke till: Object.keys(obj) inuti själva for...of-uttrycket utvärderas bara en enda gång totalt, inte en gång per iteration – att spara resultatet i en egen namngiven variabel är ändå ofta tydligare att läsa.
Från utforskande kod till färdig lösning
Section titled “Från utforskande kod till färdig lösning”Koden ovan byggdes utforskande, steg för steg, med tillfälliga variabelnamn och lite extra krångel på vägen – helt normalt när man löser ett nytt problem, och något att städa upp i efterhand snarare än att sträva efter en perfekt lösning direkt. Den färdiga referenslösningen bryter ut logiken till en separat modul (text-analyzer.js) med tre rena, exporterade funktioner – en för att dela upp text i ord, en för att räkna ordfrekvens, och en för att hitta de mest förekommande orden – och finns i två varianter: en med samma traditionella teknik som visas här, och en som använder nyare, mer kompakta ES6-konstruktioner (itererande array-metoder) som kursen återkommer till senare.
Klasser
Section titled “Klasser”I JavaScript kan objekt konstrueras på många olika sätt.
Allt handlar om prototypkedjor
Section titled “Allt handlar om prototypkedjor”Innan klass-syntax fanns i JavaScript byggdes prototypkedjor upp för hand, ofta med hjälp av det som kallas “factory”-designmönstret – en funktion som skapar och returnerar ett objekt:
const pioneerPrototype = { says (text) { return `${this.firstName} says "${text}"` }}
const createPioneer = function (firstName, lastName) { const pioneer = Object.create(pioneerPrototype) pioneer.firstName = firstName pioneer.lastName = lastName return pioneer}
const lovelace = createPioneer('Ada', 'Lovelace')Object.create(pioneerPrototype) skapar ett nytt objekt vars interna __proto__ pekar på pioneerPrototype. Nästan alla objekt i JavaScript har en sådan kedja av ett eller flera prototypobjekt – det är språkets viktigaste sätt att återanvända funktionalitet mellan objekt.
Klassdeklaration
Section titled “Klassdeklaration”Klass-syntax är ett kompakt sätt att sätta upp exakt samma sorts prototypkedja:
class Pioneer { constructor (firstName, lastName) { this.firstName = firstName this.lastName = lastName }
says (text) { return `${this.firstName} says "${text}"` }}
const lovelace = new Pioneer('Ada', 'Lovelace')En klass är i grunden bara en funktion – typeof Pioneer ger 'function'. Precis som för alla funktioner skapas automatiskt egenskapen prototype, vars värde är ett objekt som i sin tur innehåller egenskapen constructor. says-metoden hamnar på Pioneer.prototype, inte direkt på varje instans – alla instanser delar alltså samma metodobjekt.
Varför klasser och inte “constructor/prototype”?
Section titled “Varför klasser och inte “constructor/prototype”?”Innan klass-syntax fanns användes vanliga konstruktorfunktioner tillsammans med manuell .prototype-tilldelning för att uppnå samma sak. Klasser föredras idag av flera skäl: de används genomgående när man skriver webbkomponenter, ramverk som React och Angular bygger på dem, och JavaScript-motorer är bra på att optimera kod skriven med klass-syntax. Arv är också betydligt enklare att implementera med extends, även från inbyggda konstruktorfunktioner.
Det finns baksidor att vara medveten om: skillnaden mellan hur klasskod ser ut och vad som faktiskt sker under huven är stor, det är lockande att stoppa för mycket funktionalitet i en och samma klass, och arv är så enkelt att tillämpa att det ibland görs i för stor omfattning.
Flera sätt att definiera en klass
Section titled “Flera sätt att definiera en klass”Utöver klassdeklaration (class Pioneer { ... }) finns klassuttryck, både anonyma och namngivna:
// Anonymt klassuttryckconst Pioneer = class { ... }
// Namngivet klassuttryckconst Pioneer = class MyClass { ... }Oavsett hur klassen definierats skapas en instans med new följt av klassnamnet och eventuella argument.
Klassdefinitionens delar
Section titled “Klassdefinitionens delar”Alla delar av en klassdeklaration ger motsvarande egenskap på Pioneer.prototype – förutom de som föregås av static, som istället hamnar direkt på klassen (funktionen) själv:
class Pioneer { constructor (firstName, lastName) { this.firstName = firstName this.lastName = lastName }
static from (other) { return new Pioneer(other.firstName, other.lastName) }
get fullName () { return `${this.firstName} ${this.lastName}` }
says (text) { return `${this.firstName} says "${text}"` }}
const lovelace = new Pioneer('Ada', 'Lovelace')const copy = Pioneer.from(lovelace)constructor anropas automatiskt när en ny instans skapas och kan uteslutas helt om ingen initiering behövs. fullName är en getter – en metod som beter sig som en vanlig (skrivskyddad) egenskap när den läses, utan parenteser vid anropet: lovelace.fullName, inte lovelace.fullName(). En static-metod som from anropas direkt på klassen (Pioneer.from(...)), inte på en instans, och används ofta som en alternativ “fabrik” för att skapa objekt – här en kopia baserad på ett annat objekts egenskaper.
Kapsla in kod med klasser
Section titled “Kapsla in kod med klasser”Ett enkelt exempel: en tärning. Ett första försök håller ett värde och en metod som slumpar fram ett nytt:
class Die { constructor (faceValue) { this.faceValue = faceValue }
roll () { this.faceValue = Math.floor(Math.random() * 6) + 1 }}
const die = new Die()Problemet: konstruktorn initierar inte objektet på ett bra sätt – faceValue sätts inte till ett giltigt slumpat värde direkt, och antalet sidor är hårdkodat till sex. Ett andra försök löser båda: konstruktorn anropar roll direkt, och antalet sidor blir en parameter med ett standardvärde:
class Die { constructor (maxFaceValue = 6) { this.maxFaceValue = maxFaceValue this.roll() }
roll () { this.faceValue = Math.floor(Math.random() * this.maxFaceValue) + 1 }}
const die = new Die()console.log(die.faceValue) // ett tal mellan 1 och 6Skickas inget argument med skapas en sexsidig tärning; anges ett annat värde (t.ex. new Die(20)) skapas en tärning med det antalet sidor istället. Ett kvarstående problem: faceValue och maxFaceValue går fortfarande att sätta till vilket värde som helst utifrån, eftersom inget skyddar de “riktiga” egenskaperna – någonting kommande presentationer tar upp.
Läsanvisningar
Section titled “Läsanvisningar”Under läsanvisningar hittar du veckans kapitel som ska läsas i kurslitteraturen. Planera upp och dela upp läsningen under veckan.
Övningar
Section titled “Övningar”Under Övningsuppgifter hittar du övningsuppgifter som det är lämpligt att du arbetar med under veckan.
Seminarier + individuell handledning
Section titled “Seminarier + individuell handledning”Examinationsuppgift A2
Section titled “Examinationsuppgift A2”Nu bör du vara i full färd med att färdigställa examinationsuppgift A2. (För att komma igång se Examinationsuppgifter->Om->Kom Igång).
Här kan du läsa om hur du lämnar in din uppgift via en MR.
Utmaningar
Section titled “Utmaningar”Det finns en del utmaningar att titta på även denna veckan. Tänk att utmaningar kan vara bra för att komma igång med egna kodexempel kring veckans fokus.
Studiefrågor
Section titled “Studiefrågor”Här är ett antal frågor som du kan försöka besvara och diskutera tillsammans med din AI-assistent. Tveka inte att lyfta någon av frågorna till handledningen för att fortsätta diskussionen med lärare och studiekamrater.
- Ge mig en kort förklaring till begreppet klasser i JavaScript.
- Sparar man en klass i sin egen fil och använder man då import och export?
- Vilka är de grundläggande konstruktionerna i en klass?
- Vad innebär begreppet private kontra public i en klass?
- Bör man använda private eller public på properties och metoder?
- Jag vill lära mig att skydda data, jag har hört begreppet “encapsulation”.
- Visa mig hur getters och setters fungerar i en klass.
- Vad kan man säga kort om fördelar med objektorienterad programmering?
- Jag har lärt mig om JavaScript objektliteral och hur man skapar objekt i JavaScript med object.create. Hur förhåller det sig till klasser?
- Är det vanligt att skriva objektorienterad kod med klasser i JavaScript?
Här är en utskrift från hur det såg ut när jag diskuterade ovan frågor med min AI-assistent Ajj.
Här följer tips till hur du kan prioritera bland kursmaterialet för att komma igång med veckan.
- Det finns en deadline för A2, sikta på att möta den. Missa inte inspelningen om
modesom kan ge dig tips. - När du är klar med A2 så kan du redan nu börja titta på och läsa in dig på kraven för A3. Du kommer känna igen dig i strukturen på uppgiften.
- Föreläsningen om klasser ger dig bakgrunden till vad objektorientering handlar om och hur koden är tänkt att skrivas i A3.
