JavaScript's `Object`-klasse

JavaScript's `Object`-klasse

Denne artikel forklarer Object-klassen i JavaScript.

Denne artikel forklarer Object-klassen i JavaScript, inklusiv praktiske eksempler.

YouTube Video

JavaScript's Object-klasse

Object er et indbygget objekt, som fungerer som grundlag for alle objekter i JavaScript. Mange af sprogets kernefunktioner, såsom egenskabshåndtering, arv (prototypekæde), enumerering, kloning og frysning, leveres gennem Objects adfærd.

Object.create

Der er flere måder at oprette objekter på, og du bør vælge den passende metode afhængigt af dit formål.

Objekt-litteral (mest almindeligt)

Den følgende kode viser den simpleste og mest læsbare måde at oprette et objekt på.

 1// Create an object using object literal
 2const user = {
 3  name: "Alice",
 4  age: 30,
 5  greet() {
 6    return `Hello, I'm ${this.name}`;
 7  }
 8};
 9
10console.log(user.greet()); // "Hello, I'm Alice"
  • I dette eksempel defineres egenskaber og metoder ved hjælp af litteraler. Det er enkelt og giver generelt overlegen ydeevne.

new Object()-konstruktøren

Object-konstruktøren anvendes sjældent, men det er nyttigt at forstå dens funktion.

1// Create an object using the Object constructor
2const objFromCtor = new Object();
3objFromCtor.x = 10;
4objFromCtor.y = 20;
5
6console.log(objFromCtor); // { x: 10, y: 20 }
  • new Object() returnerer et tomt objekt, men litteralet {} er kortere og mere almindeligt.

Angivelse af prototype med Object.create

Object.create bruges til at oprette et objekt med en bestemt prototype.

1// Create an object with a specified prototype
2const proto = { hello() { return "hi"; } };
3const obj = Object.create(proto);
4obj.name = "Bob";
5
6console.log(obj.hello()); // "hi"
7console.log(Object.getPrototypeOf(obj) === proto); // true
  • Object.create er ideel til arv-baseret objektdesign og giver dig mulighed for præcist at styre prototypekæden.

Egenskabsattributter og -deskriptorer

Egenskaber har attributter såsom 'value', 'writable', 'enumerable' og 'configurable', som detaljeret kan kontrolleres med Object.defineProperty.

Grundlæggende eksempel på brug af defineProperty

Derefter følger et eksempel på at definere ikke-enumerérbare og skrivebeskyttede egenskaber ved hjælp af defineProperty.

 1// Define a non-enumerable read-only property
 2const person = { name: "Carol" };
 3
 4Object.defineProperty(person, "id", {
 5  value: 12345,
 6  writable: false,
 7  enumerable: false,
 8  configurable: false
 9});
10
11console.log(person.id); // 12345
12console.log(Object.keys(person)); // ["name"] — "id" is non-enumerable
13person.id = 999; // silently fails or throws in strict mode
14console.log(person.id); // still 12345
  • Ved at bruge defineProperty kan du præcist kontrollere egenskabers opførsel såsom enumeration, omskrivning og sletning.

Accessor-egenskaber (getter / setter)

Med accessorer kan du indsætte logik, når egenskaber læses eller skrives.

 1// Use getter and setter to manage internal state
 2const data = {
 3  _value: 1,
 4  get value() {
 5    return this._value;
 6  },
 7  set value(v) {
 8    if (typeof v === "number" && v > 0) {
 9      this._value = v;
10    } else {
11      throw new Error("value must be a positive number");
12    }
13  }
14};
15
16console.log(data.value); // 1
17data.value = 5;
18console.log(data.value); // 5
19// data.value = -1; // would throw
  • Med getter og setter kan adgang til egenskaber behandles som et eksternt API, hvor du kan tilføje validering eller sideeffekter.

Prototype og arv (prototype / __proto__ / Object.getPrototypeOf)

Arv i JavaScript er baseret på prototypekæden og ikke på klasser. Objekter kan referere til andre objekter som deres prototype.

Object.getPrototypeOf og Object.setPrototypeOf

Følgende eksempel viser, hvordan man inspicerer og sætter prototyper.

1// Inspect and change prototype
2const base = { speak() { return "base"; } };
3const derived = Object.create(base);
4console.log(Object.getPrototypeOf(derived) === base); // true
5
6const other = { speak() { return "other"; } };
7Object.setPrototypeOf(derived, other);
8console.log(derived.speak()); // "other"
  • Object.getPrototypeOf henter prototypen for et objekt.
  • Object.setPrototypeOf ændrer prototypen af et eksisterende objekt, men bør bruges forsigtigt, da det kan påvirke ydeevnen.

Vigtige indbyggede metoder

Vi vil tydeligt forklare de mest anvendte og vigtige metoder udvalgt fra instansmetoderne leveret af Object.prototype, samt de statiske metoder som tilhører Object.

hasOwnProperty, isPrototypeOf, toString, valueOf

hasOwnProperty, isPrototypeOf, toString og valueOf definerer objekters grundlæggende opførsel.

 1// Demonstrate prototype methods
 2const base = { greet() { return "hello"; } };
 3const child = Object.create(base);
 4const a = { x: 1 };
 5
 6console.log(a.hasOwnProperty("x")); // true
 7console.log(a.hasOwnProperty("toString")); // false — toString is inherited
 8
 9console.log(a.toString()); // "[object Object]" by default
10
11console.log(base.isPrototypeOf(child)); // true
12console.log(Object.prototype.isPrototypeOf(child)); // true
  • hasOwnProperty er en vigtig metode til at tjekke, om en egenskab findes direkte på objektet.
  • isPrototypeOf tjekker, om mål-objektet har sig selv som prototype.

Object.keys, Object.values, Object.entries

Object.keys, Object.values og Object.entries returnerer lister over et objekts egne enumererbare egenskaber. De er nyttige til iteration og transformation.

 1// Keys, values and entries
 2const item = { id: 1, name: "Widget", price: 9.99 };
 3
 4// ["id", "name", "price"]
 5console.log(Object.keys(item));
 6
 7// [1, "Widget", 9.99]
 8console.log(Object.values(item));
 9
10// [["id",1], ["name","Widget"], ["price",9.99]]
11console.log(Object.entries(item));
  • Disse bruges ofte til at iterere og transformere objekter.

Object.assign

Object.assign bruges til overfladisk kopiering og sammensmeltning. Bemærk, at prototyper og accessor-egenskaber ikke kopieres.

1// Shallow copy / merge using Object.assign
2const target = { a: 1 };
3const source = { b: 2 };
4const result = Object.assign(target, source);
5
6console.log(result); // { a: 1, b: 2 }
7console.log(target === result); // true (merged into target)
  • For indlejrede objekter kopieres kun referencerne, så du har brug for en anden implementering til dyb kloning.

Object.freeze, Object.seal, Object.preventExtensions

Object.freeze, Object.seal og Object.preventExtensions styrer objekters mutabilitet.

 1// Freeze vs seal vs preventExtensions
 2const obj = { a: 1 };
 3Object.freeze(obj);
 4obj.a = 2; // fails silently or throws in strict mode
 5delete obj.a; // fails
 6
 7const obj2 = { b: 2 };
 8Object.seal(obj2);
 9obj2.b = 3; // allowed
10// delete obj2.b; // fails
11
12const obj3 = { c: 3 };
13Object.preventExtensions(obj3);
14obj3.d = 4; // fails
  • freeze er den strengeste; den forhindrer enhver ændring af objektets egenskaber.
  • seal forhindrer tilføjelse eller sletning af egenskaber, men tillader ændring af eksisterende egenskabers værdier.
  • preventExtensions forhindrer kun tilføjelse af nye egenskaber; eksisterende egenskaber kan stadig ændres eller slettes.

Objektegenskabers enumerering, rækkefølge og for...in / for...of

for...in enumererer navne på enumererbare egenskaber, men inkluderer også egenskaber fra prototypekæden, så det bruges ofte sammen med hasOwnProperty. Object.keys() kombineret med for...of er sikrere og gør dine intentioner tydeligere.

 1// Safe enumeration
 2const obj = Object.create({ inherited: true });
 3obj.own = 1;
 4
 5for (const key in obj) {
 6  if (obj.hasOwnProperty(key)) {
 7    console.log("own prop:", key);
 8  } else {
 9    console.log("inherited prop:", key);
10  }
11}
12
13for (const key of Object.keys(obj)) {
14  console.log("key via Object.keys:", key);
15}
  • Reglerne for egenskabsenumerering er defineret i ECMAScript-specifikationen, og der er tilfælde, hvor rækkefølgen er garanteret, og andre hvor ikke. Som hovedregel er nøgler, der tolkes som tal, sorteret i stigende rækkefølge, mens andre nøgler følger indsættelsesrækkefølgen.

Kloning og dyb kopiering

Der findes to typer objektkopiering: overfladisk kopiering ved hjælp af Object.assign eller spredningssyntaksen, og dyb kopiering ved hjælp af rekursive metoder. Det er vigtigt at bruge dem korrekt afhængigt af situationen.

Overfladisk kopiering (spread-syntax / Object.assign)

1// Shallow copy with spread operator
2const original = { a: 1, nested: { x: 10 } };
3const shallow = { ...original };
4shallow.nested.x = 99;
5console.log(original.nested.x); // 99 — nested object is shared
  • Ved overfladisk kopiering deler indlejrede objekter referencer, så ændringer i det oprindelige objekt kan påvirke kopien.

Simpel dyb kopiering (med visse forbehold)

At bruge JSON-tricket er en hurtig måde at lave en dyb kopiering, men det har ulemper som tab af funktioner, Date, cirkulære referencer og undefined-værdier. For ægte dyb kloning skal du bruge et dedikeret bibliotek.

1// Deep clone using JSON methods — limited use-cases only
2const source = { a: 1, d: new Date(), nested: { x: 2 } };
3const cloned = JSON.parse(JSON.stringify(source));
4console.log(cloned); // ok for plain data, but Date becomes string, functions lost
  • JSON-baserede metoder er praktiske til hurtigt at håndtere simple data, men i almindelige tilfælde kan deres opførsel fejle.

Mixins og objektsammensætning

I stedet for multipel arv anvendes ofte et mønster, hvor adfærd sammensættes med mixins.

 1// Simple mixin function
 2const canEat = {
 3  eat() { return `${this.name} eats`; }
 4};
 5const canWalk = {
 6  walk() { return `${this.name} walks`; }
 7};
 8
 9function createPerson(name) {
10  const person = { name };
11  return Object.assign(person, canEat, canWalk);
12}
13
14const p = createPerson("Dana");
15console.log(p.eat()); // "Dana eats"
16console.log(p.walk()); // "Dana walks"
  • Mixins er fleksible, men vær opmærksom på navnekollisioner for egenskaber og testbarhed.

Almindelige faldgruber og bedste praksis

Her er nogle almindelige faldgruber og bedste praksis.

  • Mutabilitet Objekter er som standard mutable (kan ændres). I applikationer med tilstandshåndtering bør du overveje uforanderlige datastrukturer ved at bruge Object.freeze eller et uforanderligt bibliotek.

  • Prototype-forurening Hvis man flettes eksterne data direkte ind i et objekt med Object.assign eller løkker, kan det give uventede bivirkninger med særlige egenskaber som __proto__ eller constructor, hvilket skaber sikkerhedsrisici. Filtrer brugerinput, før du sammenfletter det direkte.

  • Faldgruber med for...in for...in enumererer også prototype-egenskaber, så brug hasOwnProperty til at tjekke. Det er mere klart at bruge Object.keys.

  • Forkert brug af overfladisk kopiering Overvej, om du har brug for dyb kopiering for at forhindre, at ændringer i indlejrede objekter påvirker det oprindelige objekt.

Praktisk eksempel: Immutabel objekt-opdateringsmønster

Mønstre, hvor man returnerer et nyt objekt uden at ændre tilstanden direkte, bruges ofte i React og lignende biblioteker.

 1// Immutable update example
 2const state = { todos: [{ id: 1, text: "Buy milk", done: false }] };
 3
 4// Toggle todo done immutably
 5function toggleTodo(state, todoId) {
 6  return {
 7    ...state,
 8    todos: state.todos.map(t => t.id === todoId ? { ...t, done: !t.done } : t)
 9  };
10}
11
12const newState = toggleTodo(state, 1);
13console.log(state.todos[0].done); // false
14console.log(newState.todos[0].done); // true
  • Denne kode er et eksempel på at oprette et nyt tilstandsobjekt uden direkte at ændre det oprindelige state-objekt. toggleTodo-funktionen kopierer todos-arrayet og returnerer et nyt objekt, hvor kun det ønskede element er ændret, så den oprindelige state forbliver uændret.
  • Immutabel opdatering minimerer bivirkninger og gør tilstandshåndtering enklere.

Praktisk eksempel: Sikker sammensmeltning (være opmærksom på prototype-forurening)

Når du fletter ekstern JSON, så ignorer __proto__ for at forhindre prototype-forurening.

 1// Safe merge ignoring __proto__ keys
 2function safeMerge(target, source) {
 3  for (const key of Object.keys(source)) {
 4    if (key === "__proto__" || key === "constructor") continue;
 5    target[key] = source[key];
 6  }
 7  return target;
 8}
 9
10const target = {};
11const source = JSON.parse('{"a":1,"__proto__":{"polluted":true}}');
12safeMerge(target, source);
13console.log(target.polluted); // undefined — safe
14console.log({}.polluted); // undefined — prototype not polluted
  • Denne type beskyttelse er også vigtig i biblioteker og frameworks.

Overvejelser omkring ydeevne

Med hensyn til ydeevne, overvej følgende punkter:.

  • Undgå hyppige ændringer af prototype (Object.setPrototypeOf) eller dynamisk tilføjelse/fjernelse af egenskaber, da det hæmmer motorens optimering.
  • Når du genererer mange små objekter, bliver optimering mere effektiv, hvis du bruger objekter med en ensartet struktur (det samme sæt af egenskaber).
  • Dyb kopiering er ressourcekrævende. Begræns deres brug eller overvej at anvende diff-baserede opdateringer.

Sammendrag

Object er central i JavaScript og tilbyder mange funktioner som objektoprettelse, egenskabskontrol, arv, kopiering og håndtering af mutabilitet. Det er vigtigt at forstå API'er såsom Object.defineProperty, Object.assign og Object.freeze og at designe omhyggeligt for at undgå faldgruber som prototypeforurening og overfladisk kopiering.

Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.

YouTube Video