Klassen `Object` i JavaScript

Klassen `Object` i JavaScript

Den här artikeln förklarar klassen Object i JavaScript.

Den här artikeln förklarar klassen Object i JavaScript, inklusive praktiska exempel.

YouTube Video

Klassen Object i JavaScript

Object är ett inbyggt objekt som fungerar som basen för alla JavaScript-objekt. Många av språkets kärnfunktioner, såsom egenskapshantering, arv (prototypkedja), uppräkning, kloning och frysning, tillhandahålls genom Object-beteendet.

Object.create

Det finns flera sätt att skapa objekt, och du bör använda dem på rätt sätt beroende på ditt syfte.

Objektliteral (det vanligaste sättet)

Följande kod visar det enklaste och mest lättlästa sättet att skapa ett objekt.

 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 det här exemplet definieras egenskaper och metoder med hjälp av litteraler. Det är enkelt och ger vanligtvis överlägsen prestanda.

Konstruktorn new Object()

Konstruktorn Object används sällan, men det är bra att förstå dess beteende.

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() returnerar ett tomt objekt, men literalet {} är kortare och vanligare.

Att specificera prototypen med Object.create

Object.create används för att skapa ett objekt med en specificerad prototyp.

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 är idealisk för arvbaserad objektdesign och gör att du kan kontrollera prototypkedjan exakt.

Egenskapsattribut och deskriptorer

Egenskaper har attribut som 'value', 'writable', 'enumerable' och 'configurable', vilka kan detaljstyras med Object.defineProperty.

Grundläggande exempel på att använda defineProperty

Härnäst följer ett exempel på hur man definierar icke-enumererbara och skrivskyddade egenskaper med 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
  • Med defineProperty kan du exakt styra egenskapers beteende såsom enumerering, överskrivning och borttagning.

Åtkomstegenskaper (getter / setter)

Med accessorer kan du införa logik vid läsning och skrivning av egenskaper.

 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 och setter kan du behandla egenskapsåtkomst som ett externt API och lägga till validering eller bieffekter.

Prototyp och arv (prototype / __proto__ / Object.getPrototypeOf)

Arv i JavaScript baseras på prototypkedjan, inte på klasser. Objekt kan referera till andra objekt som sina prototyper.

Object.getPrototypeOf och Object.setPrototypeOf

Följande exempel visar hur man inspekterar och 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 hämtar prototypen för ett objekt.
  • Object.setPrototypeOf ändrar prototypen för ett existerande objekt, men bör användas med försiktighet då det kan påverka prestandan.

Viktiga inbyggda metoder

Vi kommer tydligt att förklara de mest använda och viktiga metoderna som valts från instansmetoderna som tillhandahålls av Object.prototype, samt de statiska metoder som ägs av Object.

hasOwnProperty, isPrototypeOf, toString, valueOf

hasOwnProperty, isPrototypeOf, toString och valueOf definierar objektens grundläggande beteende.

 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 är en viktig metod för att kontrollera om en egenskap finns direkt på objektet.
  • isPrototypeOf kontrollerar om målobjektet har det som prototyp.

Object.keys, Object.values, Object.entries

Object.keys, Object.values och Object.entries returnerar listor med objektets egna uppräkningsbara egenskaper. De är användbara för iteration och 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));
  • Dessa används ofta för att iterera över och transformera objekt.

Object.assign

Object.assign används för ytlig kopiering och sammanslagning. Observera att prototyper och åtkomstegenskaper inte kopieras.

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)
  • För nästlade objekt kopieras endast referenserna, så du behöver en annan implementering för djup kloning.

Object.freeze, Object.seal, Object.preventExtensions

Object.freeze, Object.seal och Object.preventExtensions styr objektens förändringsbarhet.

 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 är striktast; det förhindrar alla ändringar av objektets egenskaper.
  • seal förhindrar att egenskaper läggs till eller tas bort, men tillåter ändringar av befintliga egenskapers värden.
  • preventExtensions förhindrar endast tillägg av nya egenskaper; befintliga egenskaper kan fortfarande ändras eller tas bort.

Objektuppräkning, ordning och for...in / for...of

for...in räknar upp uppräkningsbara egenskapsnamn, men inkluderar också egenskaper från prototypkedjan, så den används ofta ihop med hasOwnProperty. Att kombinera Object.keys() med for...of är säkrare och gör dina avsikter tydligare.

 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}
  • Reglerna för egenskapsuppräkning definieras i ECMAScript-specifikationen, och ibland är uppräkningsordningen garanterad, ibland inte. Som en allmän regel ordnas nycklar som tolkas som siffror i stigande ordning, medan andra nycklar följer insättningsordningen.

Kloning och djupkopiering

Det finns två typer av kopiering av objekt: ytlig kopiering med Object.assign eller spridningssyntaxen, och djup kopiering med rekursiva metoder. Det är viktigt att använda dem på rätt sätt beroende på situationen.

Ytlig kopiering (spridningssyntax / 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
  • Vid ytlig kopiering delar nästlade objekt referenser, så ändringar i originalobjektet kan påverka kopian.

Enkel djupkopiering (med förbehåll)

Att använda JSON-tricket är ett snabbt sätt att skapa en djupkopia, men det har nackdelar som att funktioner, Date, cirkulära referenser och undefined-värden försvinner. För äkta djupkloning behöver du använda ett särskilt 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-baserade metoder är praktiska för att snabbt hantera enkel data, men i vanliga fall kan deras beteende orsaka problem.

Mixins och objektkomposition

Istället för multipelt arv används ofta mönstret att sätta ihop beteenden 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 är flexibla, men man måste vara försiktig med egenskapsnamnskollisioner och testbarhet.

Vanliga fallgropar och bästa praxis

Här är några vanliga fallgropar och bästa praxis.

  • Föränderlighet Objekt är föränderliga som standard. I applikationer med tillståndshantering bör du överväga oföränderliga datastrukturer med Object.freeze eller ett bibliotek för immutabilitet.

  • Prototypförorening Att slå ihop extern data direkt till ett objekt med Object.assign eller loopar kan orsaka oväntade bieffekter med speciella egenskaper som __proto__ eller constructor, vilket skapar säkerhetsrisker. Filtrera användarinmatning innan du slår ihop den direkt.

  • Fallgropar med for...in for...in räknar även upp prototyps-egenskaper, så kontrollera med hasOwnProperty. Att använda Object.keys är tydligare.

  • Missbruk av ytlig kopiering Överväg om du behöver djupkopiering för att förhindra att ändringar i nästlade objekt påverkar originalet.

Praktiskt exempel: Mönster för oföränderlig objektsuppdatering

Mönster som returnerar ett nytt objekt utan att direkt ändra tillståndet används ofta i React och liknande bibliotek.

 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
  • Den här koden är ett exempel på att skapa ett nytt tillståndsobjekt utan att direkt ändra det ursprungliga state-objektet. toggleTodo-funktionen kopierar todos-arrayen och returnerar ett nytt objekt med endast det aktuella elementet ändrat, så det ursprungliga state förblir oförändrat.
  • Oföränderliga uppdateringar minskar bieffekter och gör tillståndshanteringen enklare.

Praktiskt exempel: Säker sammanslagning (Var försiktig med prototypförorening)

Vid sammanslagning av extern JSON, ignorera __proto__ för att undvika prototypförorening.

 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
  • Den här typen av skydd är också viktigt i bibliotek och ramverk.

Prestandaöverväganden

Vad gäller prestanda, tänk på följande punkter:.

  • Undvik frekventa prototypändringar (Object.setPrototypeOf) eller dynamiska tillägg/borttagningar av egenskaper, eftersom dessa förhindrar motoroptimeringar.
  • När du skapar många små objekt blir optimering mer effektiv om du använder objekt med en enhetlig struktur (samma uppsättning egenskaper).
  • Djupkopiering är kostsamt. Minimera användningen eller överväg diff-baserade uppdateringar.

Sammanfattning

Object är centralt i JavaScript och möjliggör funktioner som objekt­skapande, egenskapsstyrning, arv, kopiering och hantering av föränderlighet. Det är viktigt att förstå API:er som Object.defineProperty, Object.assign och Object.freeze, och att designa noggrant för att undvika fallgropar som prototypförorening och ytlig kopiering.

Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.

YouTube Video