Översikt
Fjärde kursveckan! Veckan inleds med en repetition och ett större exempel för att sedan titta på veckans huvudtema, Arrayer! Information om veckans upplägg annonseras löpande i kursens Zulip-kanal.
Stödjande inspelningar
Section titled “Stödjande inspelningar”Exempel - Vilket Unicode-värde
Section titled “Exempel - Vilket Unicode-värde”Kursledningen inleder till viss del med repetition för att förstärka delar som rör funktioner och moduler.
Exempelkoden hittar du på Gitlab.
Utgångsläget: allt i en sekvens
Section titled “Utgångsläget: allt i en sekvens”Exemplet startar som ett litet, fungerande program: det läser in en textrad och ett index från användaren (via paketet readline-sync) och skriver ut vilket tecken och Unicode-värde som finns på det indexet, tills ett negativt index avslutar programmet. Hela logiken ligger som en enda lång sekvens av satser i app.js – precis det som gör koden svår att testa och underhålla när ett program växer.
Ett första steg mot bättre struktur: identifiera vilka satser som hör ihop, och bryt ut dem till egna funktioner.
Steg 1: bryt ut satser till funktioner
Section titled “Steg 1: bryt ut satser till funktioner”Två tydliga kandidater identifieras: utskriften av hjälptexten och läsningen av ett index. De blir varsin funktion:
const printInstructions = function () { console.log('...')}
const getIndexFromUser = function () { return Number.parseInt(readlineSync.question('Enter an index (negative value to quit): '))}getIndexFromUser används på två ställen i originalkoden (en gång innan loopen, en gång sist i loopens kropp) – att bryta ut den till en funktion tar bort just den upprepningen. En funktion som getIndexFromUser behöver inga parametrar eftersom den bara returnerar ett värde den själv hämtar in.
Steg 2: samla allt i en main-funktion
Section titled “Steg 2: samla allt i en main-funktion”Med flera funktioner definierade blir det otydligt var exekveringen egentligen börjar. Lösningen är att samla den kvarvarande “lösa” koden i en egen funktion, av tradition ofta kallad main (ett arv från C-liknande språk):
const main = function () { // all kvarvarande kod, inklusive while-loopen}
main()Anropet av main() sist i filen blir den enda platsen exekveringen faktiskt startar – tydligare än att låta lös kod ligga utspridd bland funktionsdeklarationerna.
Steg 3: bryt ut funktionerna till en egen modul
Section titled “Steg 3: bryt ut funktionerna till en egen modul”Fortfarande ligger alla funktioner i samma fil som main. Nästa steg i omstruktureringen är att gruppera de funktioner som hör ihop och flytta dem till en egen modul – samma princip som att gruppera satser i funktioner, bara ett steg upp. En ny fil (namngiven i kebab-case, i linje med Node-modulers namnkonvention) samlar hjälpfunktionerna, och exporterar dem med namngivna exports:
export const printInstructions = function () { ... }export const getIndexFromUser = function () { ... }// osv.import * as UnicodePicker from './unicode-picker.js'
const main = function () { UnicodePicker.printInstructions() // ...}Ett par detaljer som gav verkliga felmeddelanden på vägen: importsökvägen till en egen ES-modul måste innehålla filändelsen (.js) – till skillnad från CommonJS, där det brukar fungera utan. Och en modul utan export default kan inte importeras med ett vanligt import Namn from ... – då krävs antingen namnrymdsimport (import * as Namn from ...) för att få tag i alla namngivna exports samlade under ett gemensamt namn, eller en specifik namngiven import (import { specifikFunktion } from ...) om bara någon enstaka funktion behövs. export default avråds ifrån här – att alltid skriva ut exakt vad som exporteras gör koden tydligare.
Slutresultatet: app.js innehåller bara main, som läses nästan som en innehållsförteckning över vad programmet gör; all den specifika logiken ligger samlad i en egen, namngiven modul.
Lärdomar
Section titled “Lärdomar”Att lägga ett så här litet exempel (ett tjugotal rader) i flera filer och moduler kan kännas onödigt omständigt för just det här problemet – poängen är träningen i sig, eftersom samma disciplin blir avgörande först när ett program vuxit till tusentals rader. Det är för sent att lära sig strukturera kod först då. Detsamma gäller dokumentation: att skriva en funktions JSDoc-kommentar innan man skriver dess innehåll tvingar fram klarhet i vad funktionen faktiskt ska göra, istället för att dokumentationen blir en eftertanke som ofta uteblir helt i tidspress.
Arrayer
Section titled “Arrayer”Att kunna organisera större dataset i listor, arrayer, är mycket kraftfullt. En lång föreläsning på ämnet ligger till grund för dessa delar.
Varför arrayer?
Section titled “Varför arrayer?”En variabel kan bara hålla ett värde, och en funktion kan bara returnera ett värde. Men mycket av det vi vill programmera handlar inte om enstaka värden utan om samlingar av värden – en lista med vänner, en kundvagn, rader i en handlingslista. Lösningen är en referens till en array eller ett objekt, där själva samlingen kan innehålla hur många värden som helst.
En array är en ordnad lista med värden. Ordnad betyder här att raderna behåller sin inbördes ordning – inte att värdena är sorterade.
Skapa en array
Section titled “Skapa en array”Det rekommenderade sättet är en array-literal med hakparenteser:
const groceries = [ 'mjölk', 'ägg', 'pasta', 'ketchup', 'nudlar', 'havregryn']Varje värde i listan kallas ett element, och elementen numreras från och med 0 – ett 0-baserat index. groceries[4] ger alltså det femte elementet, 'nudlar', inte det fjärde.
Det går också att skapa en array med new Array(...), men det avråds ifrån – konstruktorn beter sig olika beroende på hur många argument som skickas in. new Array(28, 18, 26) skapar en array med de tre talen, men new Array(10) skapar inte en array med värdet 10, utan en array med tio tomma (undefined) element. Array-literalen har inte den fällan.
Alla värden i en array bör vara av samma datatyp. JavaScript tillåter att man blandar typer ([42, 'är meningen med livet', true]), men det är en dålig idé – blir det en samling av olika typer av data hör den hemma i ett objekt istället.
Arbeta med element
Section titled “Arbeta med element”Index används både för att läsa och skriva ett elements värde:
const temperatures = [12.6, 18.7, 9.8, 13.2, 14.4]
console.log(temperatures[3]) // OUTPUT: 13.2temperatures[1] = 4.2 // tilldelar det andra elementet ett nytt värde
console.log(temperatures.length) // OUTPUT: 5length är en egenskap, inte en metod – inga parenteser.
Iterera med for-loop
Section titled “Iterera med for-loop”Att en array har ett 0-baserat index och en känd längd är precis varför for-loopen ofta ser ut som den gör:
let output = ''for (let i = 0; i < temperatures.length; i++) { output += temperatures[i] + ' '}Loopvillkoret ska vara i < length, inte i <= length – längden 5 betyder att sista giltiga indexet är 4. Försöker man läsa ett index utanför arrayen kraschar inte JavaScript (till skillnad från många andra språk) – man får undefined tillbaka istället.
Lägga till och ta bort element
Section titled “Lägga till och ta bort element”De vanligaste metoderna för att ändra en arrays storlek:
push(värde)– lägger till sistpop()– tar bort och returnerar det sista elementetunshift(värde)– lägger till förstshift()– tar bort och returnerar det första elementet
splice() kan lägga till, ta bort eller ersätta element mitt i en array, men används mer sällan.
Värdetyper och referenstyper
Section titled “Värdetyper och referenstyper”Det här är den mest centrala – och klurigaste – delen. Number (och de andra primitiva typerna) är en värdetyp: när en variabel tilldelas en annan variabels värde kopieras själva värdet, och de två variablerna blir sedan helt oberoende av varandra.
let dogAge = 7let catAge
catAge = dogAge // 7 kopieras till catAgecatAge = 3
console.log(catAge) // OUTPUT: 3console.log(dogAge) // OUTPUT: 7 – opåverkadEn array är däremot ett objekt, och objekt är referenstyper. Tilldelningen kopierar inte själva arrayen, bara en referens till den – som att ge ut ett extra koppel till samma hund. Två variabler kan referera till en och samma array:
const numbers = [5, 8, 7, 1, 6]let figures
figures = numbers // figures får en referens till SAMMA arrayfigures[0] = 42
console.log(figures) // OUTPUT: [ 42, 8, 7, 1, 6 ]console.log(numbers) // OUTPUT: [ 42, 8, 7, 1, 6 ] – båda påverkas!Anledningen till att referenser fungerar så här är prestanda: en array kan innehålla hundratusentals värden (tänk en bildfil, pixel för pixel), och att kopiera hela innehållet varje gång skulle vara orimligt dyrt. Att kopiera en referens – i praktiken en minnesadress – kostar i princip ingenting.
Referenser som argument och returvärde
Section titled “Referenser som argument och returvärde”Samma resonemang gäller när en array skickas till eller returneras från en funktion. Skickas en array in som argument arbetar funktionen på samma array som anroparen har, vilket kan ge oönskade sidoeffekter:
const logDoubledUp = function (results) { for (let i = 0; i < results.length; i++) { results[i] *= 2 } console.log(results)}
const scores = [18, 9, 23, 12, 21]logDoubledUp(scores)console.log(scores) // OUTPUT: [ 36, 18, 46, 24, 42 ] – scores har muterats!Vill man undvika det arbetar man på en kopia istället, till exempel med Array.from(), spridningsoperatorn [...array] eller (äldre) .slice():
const logDoubledUp = function (results) { const copy = Array.from(results) for (let i = 0; i < copy.length; i++) { copy[i] *= 2 } console.log(copy)}Sortera en array
Section titled “Sortera en array”sort() utan argument konverterar först elementen till strängar och jämför dem lexikografiskt – vilket ger överraskande resultat för tal (10 hamnar före 2). Ska talen sorteras numeriskt behövs en egen jämförelsefunktion:
const values = [2, 42, 10, 1, 8, 24, 4]values.sort(function (a, b) { return a - b})// OUTPUT: [ 1, 2, 4, 8, 10, 24, 42 ]Fler metoder: forEach, reduce och map
Section titled “Fler metoder: forEach, reduce och map”forEach itererar igenom värdena, reduce reducerar dem till ett enda värde (till exempel en summa), och map skapar en helt ny array genom att transformera varje värde:
const values = [2, 1, 8]
values.forEach(function (value) { console.log(value)})
const sum = values.reduce(function (a, b) { return a + b}, 0) // OUTPUT: 11
const doubledValues = values.map(value => value * 2) // OUTPUT: [ 4, 2, 16 ]Både forEach och reduce tar emot en funktion som argument, vilket gör dem till exempel på det som kallas “higher-order functions”. map-exemplet visar också en arrow function – ett kortare sätt att skriva ett funktionsuttryck på, där function och return kan uteslutas när kroppen bara innehåller en sats.
Undantag
Section titled “Undantag”Redan nu smygs en kort introduktion till undantag in.
Något kommer att gå fel
Section titled “Något kommer att gå fel”Även om koden är korrekt utifrån vad man känner till kan en applikation stöta på fel: felaktig indata, ett API som svarar annorlunda än väntat, eller helt enkelt ett misstag i koden. Det här blir extra tydligt så fort en applikation tar emot data utifrån – från en användare, ett nätverksanrop eller en extern tjänst – för då vet man aldrig med säkerhet vad som faktiskt kommer in.
const name = 'Lovelace'name = 'Hopper' // TypeError: Assignment to constant variable.console.log(name) // körs aldrigNär ett exekveringsfel inträffar kastas ett undantag, och exekveringen av programmet avbryts omedelbart där felet uppstod – satsen efter felet körs aldrig.
Vad är ett undantag?
Section titled “Vad är ett undantag?”Ett undantag är inget annat än ett objekt som representerar felet. JavaScript har flera inbyggda feltyper som används vid olika sorters fel: Error, EvalError, InternalError, RangeError, ReferenceError, SyntaxError, TypeError och URIError. I egen kod är det oftast Error och TypeError man kastar.
Kasta undantag
Section titled “Kasta undantag”Utöver att JavaScript själv kastar undantag kan man som programmerare aktivt kasta ett eget, med det reserverade ordet throw. Ett felobjekt skapas med new och tar oftast ett meddelande som argument:
const getDigitSum = function (value) { if (typeof value !== 'number') { throw new TypeError('The argument passed must be a Number.') } // summera siffrorna och returnera summan}Poängen med att kontrollera indata och kasta ett tydligt fel direkt är dels att undvika konstiga följdfel längre in i koden, dels att en funktion i praktiken kan anropas med vilket argument som helst – det finns ingen garanti för att den som anropar skickar in rätt typ.
Felobjektet har egenskaperna name (vilken feltyp) och message (det egna meddelandet), på samma sätt som vilket annat objekt som helst.
Fånga undantag
Section titled “Fånga undantag”Kod som riskerar att kasta ett undantag placeras i ett try-block. Kastas ett undantag där hoppar exekveringen direkt till catch-blocket, som får tillgång till felobjektet och kan hantera det – till exempel logga ett mer användarvänligt meddelande istället för att låta hela applikationen krascha med en teknisk stacktrace. Ett eventuellt finally-block körs alltid, oavsett om ett fel kastades eller inte.
try { const result = getDigitSum('This is not a number') console.log(`The sum of the digits is ${result}.`)} catch (err) { console.error(err.message)} finally { // körs alltid}try/catch/finally kan kombineras som try-catch, try-finally eller alla tre tillsammans. Blir det inget fel i try-blocket exekveras inte catch-blocket – men finally körs ändå.
Så länge koden själv har full kontroll (till exempel att bara skicka in tal man redan vet är tal) behövs inget try/catch. Det blir nödvändigt först när koden gör saker den inte kan garantera lyckas – och det tydligaste exemplet är nätverksanrop över internet, där en kabel kan vara av eller en server nere. Det är ett ämne som återkommer längre fram i kursen.
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”Vid det här laget är du bekant med handledningskonceptet. Ett tips är att de studenter som lyckas bäst på utbildningen är också de studenter som är aktiva på handledningstillfällena!
Examinationsuppgift A2
Section titled “Examinationsuppgift A2”Examinationsuppgift A2 finns publicerad och denna vecka bör du i alla fall läsa igenom uppgiftsbeskrivningen och kanske få igång uppgiften lokalt på din dator (se Kursvecka-03). Det räcker att du börjar arbeta på uppgiften under nästa vecka, kursvecka 05, men det skadar inte om du börjar redan i denna veckan.
Utmaningar
Section titled “Utmaningar”Antalet utmaningar minskar i takt med att övningsuppgifternas komplexitet ökar.
Studiefrågor
Section titled “Studiefrågor”Här är ett antal frågor som du kan försöka besvara och diskutera tillsammans med en AI-assistent. Tveka inte att lyfta någon av frågorna till handledningen för att fortsätta diskussionen med lärare och studiekamrater.
- Förklara grunderna i konstruktionen array i JavaScript.
- Vilka är de vanligaste inbyggda metoderna som man behöver lära sig när det gäller arrayer?
- När man jobbar med arrayer så är koden ibland funktionellt skriven och ibland mer procedurell, vad är skillnaden i de båda tankesätten?
- Finns det något särskilt jag bör tänka på om jag gör ett program som jobbar med arrayer och nummer?
- Vill du ge ett exempel på hur man kan använda undantag tillsammans med arrayer och nummer?
Här är en utskrift från hur det kan se ut när frågorna ovan diskuterats med en AI-assistent.
Här följer tips till hur du kan prioritera bland kursmaterialet för att komma igång med veckan.
- Både utmaningar och övningsuppgifterna är bra träning för att hjälpa dig att lära dig olika konstruktioner i JavaScript. Se till att kombinera praktisk programmering och felsökning tillsammans med att du tittar på föreläsningar och litteraturen.
- Denna veckan har fokus på arrayer, så se till att du lär dig grunderna i arrayer (och lite till). Du kommer att ha stor nytta av arrayer i A2.
- Börja med A2, det är mycket att läsa och om det finns någon metod som du kan börja bygga så gör det. Det är många enhetstester som skall passeras så ta ett antal varje vecka.
