Objektorienterad programmering (OOP) bygger på några grundläggande koncept. Här är de viktigaste, med enkla exempel för att visa hur de tillämpas i praktiken:
En klass är ritningen (gjutformen), medan en instans är det faktiska objektet som skapas utifrån ritningen.
// KLASSEN (Ritningen)
class Hund {
constructor(namn, ras) {
// Egenskaper (variabler som tillhör objektet)
this.namn = namn;
this.ras = ras;
}
// Metod (funktion som tillhör objektet)
skall() {
console.log(`${this.namn} säger: Voff!`);
}
}
// INSTANSER (Faktiska objekt skapade med nyckelordet "new")
const hund1 = new Hund("Karo", "Pudel");
const hund2 = new Hund("Båtsman", "Sankt Bernhard");
// Läsa egenskaper från instanserna
console.log(hund1.namn); // Karo
console.log(hund2.ras); // Sankt Bernhard
// Anropa metoder på instanserna
hund1.skall(); // Karo säger: Voff!
hund2.skall(); // Båtsman säger: Voff!
Klassen (Hund): Innehåller ingen data om en enskild hund. Den definierar bara vad en hund ska ha för egenskaper (namn, ras) och vad den ska kunna göra (skall()).
Instanserna (hund1 och hund2): Är två helt fristående objekt. De delar samma struktur och metoder, men innehåller sina egna unika värden.
Inkapsling (encapsulation) handlar om att låsa in objektets tillstånd (attribut) så att de inte kan ändras eller förstöras direkt utifrån. Med # görs attributen privata, vilket tvingar användaren att gå via objektets egna metoder eller getters/setters för att läsa eller ändra datan.
class Djur {
// Privata attribut – kan ENDAST nås inifrån klassen
#namn;
#energi;
constructor(namn, energi = 50) {
this.#namn = namn;
this.#energi = energi;
}
// Getter: Tillåter omvärlden att LÄSA namnet, men inte ÄNDRA det direkt
get namn() {
return this.#namn;
}
// Getter: Läser den privata energin
get energi() {
return this.#energi;
}
// Kontrollerad metod för att ÄNDRA det privata attributet #energi
mata(mängd) {
if (mängd <= 0) {
console.log("Du måste ge en giltig mängd mat!");
return;
}
this.#energi += mängd;
if (this.#energi > 100) {
this.#energi = 100;
}
console.log(`${this.#namn} åt och har nu ${this.#energi}% energi.`);
}
}
// --- HUR ANVÄNDER DU KODEN ---
const karo = new Djur("Karo", 30);
// 1. Läsa data säkert via getters
console.log(karo.namn); // "Karo"
console.log(karo.energi); // 30
// 2. Ändra data säkert via metoder (med validering)
karo.mata(40); // "Karo åt och har nu 70% energi."
// 3. Skyddet i praktiken:
// karo.#energi = 1000;
// ❌ SyntaxError: Private field '#energi' must be declared in an enclosing class
// karo.energi = 1000;
// ❌ Ändrar ingenting eftersom det bara finns en 'get energi()', ingen 'set'
Privata fält (#): Hindrar att någon råkar skriva felaktig data utifrån, till exempel karo.energi = -999 eller karo.energi = "massa energi".
Gränssnitt vs Logik: Omvärlden använder metoden mata(40). Regeln att energin aldrig får överstiga 100% ligger kapslad inuti klassen, där den hör hemma.
Arv (inheritance) handlar om att återanvända kod för att slippa skriva samma sak flera gånger. Genom att lägga gemensamma egenskaper och metoder i en föräldraklass (Djur) får alla underklasser (Hund, Katt) tillgång till dem automatiskt via nyckelordet extends.
JavaScript
// Föräldraklass (Basklass) med privata attribut och getters för säker åtkomst
class Djur {
#namn;
#alder;
constructor(namn, alder) {
this.#namn = namn;
this.#alder = alder;
}
get namn() { return this.#namn; }
get alder() { return this.#alder; }
ata() { console.log(`${this.namn} äter sin mat.`); }
sova() { console.log(`${this.namn} sover ganska gott.`); }
}
// Underklass som ärver från Djur och använder super()
class Hund extends Djur {
#ras;
constructor(namn, alder, ras) {
super(namn, alder);
this.#ras = ras;
}
get ras() { return this.#ras; }
hämtaBoll() {
console.log(`${this.namn} (${this.ras}) springer och hämtar bollen!`);
}
}
class Katt extends Djur {
spinna() { console.log(`${this.namn} spinner nöjt: Purrr...`); }
}
// --- HUR ANVÄNDER DU KODEN ---
const karo = new Hund("Karo", 3);
const missie = new Katt("Missie", 5);
// Båda djuren kan äta och sova eftersom de ärvt det från Djur
karo.ata(); // "Karo äter sin mat."
missie.ata(); // "Missie äter sin mat."
// De har sina egna unika färdigheter
karo.hämtaBoll(); // "Karo springer och hämtar bollen!"
missie.spinna(); // "Missie spinner nöjt: Purrr..."
DRY (Don't Repeat Yourself): Utan arv hade du behövt skriva egenskaperna namn, alder samt metoderna ata() och sova() två gånger – en gång i Hund och en gång i Katt.
Specialisering: Underklasser får allt föräldern har, men kan bygga vidare med egna unika metoder som hämtaBoll() eller spinna().
Utökning med super(): Om hunden behöver en egen unik egenskap i sin constructor (t.ex. ras) anropar man super(namn, alder) för att låta föräldraklassen ta hand om grunddatan först.
I kod innebär det att vi ger objektet enkla metoder som leka() eller sova(), så att den som använder klassen slipper räkna matte eller hålla koll på alla interna variabler själv.
JavaScript
class Djur {
#namn;
#energi;
constructor(namn) {
this.#namn = namn;
this.#energi = 50;
}
get namn() { return this.#namn; }
get energi() { return this.#energi; }
sova(timmar) {
this.#energi = Math.min(100, this.#energi + timmar * 10);
console.log(`${this.namn} har sovit och har nu ${this.energi}% energi.`);
}
förbrukaEnergi(mängd) {
if (this.#energi < mängd) return false;
this.#energi -= mängd;
return true;
}
}
class Hund extends Djur {
leka() {
if (this.förbrukaEnergi(20)) {
console.log(`${this.namn} springer efter bollen! Energi kvar: ${this.energi}%`);
} else {
console.log(`${this.namn} är för trött för att leka...`);
}
}
}
// --- HUR ANVÄNDER DU KODEN ---
const karo = new Hund("Karo");
karo.leka(); // "Karo springer efter bollen! Energi kvar: 30%"
karo.leka(); // "Karo springer efter bollen! Energi kvar: 10%"
karo.leka(); // "Karo är för trött för att leka..."
// Karo är trött, så vi ber honom sova
karo.sova(5); // "Karo har sovit och har nu 60% energi."
Förenklat gränssnitt: Den som styr hunden behöver bara skriva Karo.leka(). Man behöver inte manuellt skriva if (energi < 20) eller energi = energi - 20 varje gång hunden ska göra något.
Dold reglering: När sova(5) anropas behöver användaren inte veta hur energin räknas ut eller hur taket på max 100% sätts. Metoden städar upp och sköter allt bakom kulisserna.
Polymorfism (som betyder "många former") innebär i programmering att olika klasser kan använda exakt samma metodnamn, men utföra handlingen på sina egna unika sätt.
För eleverna innebär det stor flexibilitet: man kan behandla en samling av olika djur på ett enhetligt sätt utan att manuellt behöva kontrollera vilken ras eller art varje enskilt objekt tillhör.
// Föräldraklass (Basklass) med privat namn
class Djur {
#namn;
constructor(namn) {
this.#namn = namn;
}
// Publik getter så att underklasser och omvärlden kan läsa namnet säkert
get namn() {
return this.#namn;
}
// Gemensam metod – förälderklassens standardbeteende
gorLjud() {
console.log(`${this.namn} gör ett okänt djurljud.`);
}
}
// Underklass 1 – Överskriver (overridar) förälderns metod
class Hund extends Djur {
gorLjud() {
console.log(`${this.namn} skäller: Voff voff!`);
}
}
// Underklass 2 – Överskriver samma metod med sitt eget ljud
class Katt extends Djur {
gorLjud() {
console.log(`${this.namn} mjauar: Mjau mjau!`);
}
}
// --- HUR ANVÄNDER DU KODEN ---
// En lista med en blandning av olika djurobjekt
const djurpark = [
new Hund("Karo"),
new Katt("Missie"),
new Hund("Båtsman")
];
// Vi behöver inte veta vilken sort det är – alla djur svarar på .gorLjud()
djurpark.forEach(djur => {
djur.gorLjud();
});
// Utskrift:
// Karo skäller: Voff voff!
// Missie mjauar: Mjau mjau!
// Båtsman skäller: Voff voff!
Hund och Katt ärver metoden gorLjud() från Djur, men byter ut förälderns kod mot sin egen version.forEach) behöver du inte skriva if (djur är en hund) skäll else if (djur är en katt) mjaua. Du bara ber objektet att köra .gorLjud(), så vet JavaScript automatiskt vilken version av metoden som ska anropas.✅ Sammanfattning: