Onveranderlijkheid is een kernprincipe in functioneel programmeren, en heeft ook veel te bieden aan objectgeoriënteerde programma's. Een veranderlijk object is een object waarvan de toestand kan worden gewijzigd nadat het is gemaakt. Een onveranderlijk object is een object waarvan de toestand niet kan worden gewijzigd nadat het is gemaakt.
Wat is een voorbeeld van een onveranderlijk object in JavaScript?
In JavaScript zijn sommige ingebouwde typen (nummers, strings) onveranderlijk, maar aangepaste objecten zijn over het algemeen veranderlijk.
Enkele ingebouwde onveranderlijke JavaScript-objecten zijn 'Math', 'Date'.
Hier zijn een paar manieren om onveranderlijkheid toe te voegen/te simuleren op gewone JavaScript-objecten.
Object constante eigenschappen
Door 'writable: false' en 'configurable: false' te combineren, kunt u in wezen een constante (kan niet worden gewijzigd, opnieuw gedefinieerd of verwijderd) maken als een objecteigenschap, zoals:
let myObject = {};
Object.defineProperty(myObject, 'number', {
value: 42,
writable: false,
configurable: false,
});
console.log(myObject.number); // 42
myObject.number = 43;
console.log(myObject.number); // 42
Extensies voorkomen
Als u wilt voorkomen dat er nieuwe eigenschappen aan een object worden toegevoegd, maar de rest van de eigenschappen van het object met rust wilt laten, roept u 'Object.preventExtensions(...)' aan:
var myObject = {
a: 2,
};
Object.preventExtensions(myObject);
myObject.b = 3;
myObject.b; // undefined
In non-strict mode mislukt het creëren van 'b' stilzwijgend. In strict mode werpt het een 'TypeError'.
Verzegelen (Seal)
'Object.seal()' creëert een 'verzegeld' object, wat betekent dat het een bestaand object neemt en er in wezen 'Object.preventExtensions()' op aanroept, maar ook al zijn bestaande eigenschappen markeert als 'configurable: false'.
Dus, u kunt niet alleen geen eigenschappen meer toevoegen, maar u kunt ook geen bestaande eigenschappen opnieuw configureren of verwijderen (hoewel u hun waarden nog steeds kunt wijzigen).
Vriezen (Freeze)
'Object.freeze()' creëert een bevroren object, wat betekent dat het een bestaand object neemt en er in wezen 'Object.seal()' op aanroept, maar het markeert ook alle 'data accessor'-eigenschappen als 'writable:false', zodat hun waarden niet kunnen worden gewijzigd.
Deze aanpak is het hoogste niveau van onveranderlijkheid dat u kunt bereiken voor een object zelf, aangezien het alle wijzigingen aan het object of aan een van zijn directe eigenschappen voorkomt (hoewel, zoals hierboven vermeld, de inhoud van eventueel gerefereerde andere objecten onaangetast blijft).
var immutable = Object.freeze({});
Het bevriezen van een object staat niet toe dat er nieuwe eigenschappen aan een object worden toegevoegd en voorkomt het verwijderen of wijzigen van de bestaande eigenschappen. 'Object.freeze()' behoudt de opsombaarheid, configureerbaarheid, schrijfbaarheid en het prototype van het object. Het retourneert het doorgegeven object en creëert geen bevroren kopie.
Wat zijn de voor- en nadelen van onveranderlijkheid?
Voordelen
- Gemakkelijkere wijzigingsdetectie - Objectgelijkheid kan op een performante en eenvoudige manier worden bepaald door referentiële gelijkheid. Dit is handig voor het vergelijken van objectverschillen in React en Redux.
- Programma's met onveranderlijke objecten zijn minder ingewikkeld om over na te denken, aangezien u zich geen zorgen hoeft te maken over hoe een object in de loop van de tijd kan evolueren.
- Defensieve kopieën zijn niet langer nodig wanneer onveranderlijke objecten worden geretourneerd van of doorgegeven aan functies, aangezien er geen mogelijkheid is dat een onveranderlijk object erdoor wordt gewijzigd.
- Eenvoudig delen via referenties - Eén kopie van een object is net zo goed als een andere, dus u kunt objecten cachen of hetzelfde object meerdere keren hergebruiken.
- Draadveilig - Onveranderlijke objecten kunnen veilig worden gebruikt tussen threads in een multi-threaded omgeving, aangezien er geen risico is dat ze worden gewijzigd in andere gelijktijdig draaiende threads.
- Bij gebruik van bibliotheken zoals ImmutableJS worden objecten gewijzigd met structurele delen en is er minder geheugen nodig voor meerdere objecten met vergelijkbare structuren.
Nadelen
- Naïeve implementaties van onveranderlijke gegevensstructuren en de bewerkingen ervan kunnen leiden tot extreem slechte prestaties omdat elke keer nieuwe objecten worden gemaakt. Het wordt aanbevolen om bibliotheken te gebruiken voor efficiënte onveranderlijke gegevensstructuren en bewerkingen die gebruikmaken van structureel delen.
- Toewijzing (en deallocatie) van vele kleine objecten in plaats van het wijzigen van bestaande objecten kan een prestatie-impact veroorzaken. De complexiteit van de allocator of de garbage collector hangt meestal af van het aantal objecten op de heap.
- Cyclische gegevensstructuren zoals grafen zijn moeilijk te bouwen. Als u twee objecten hebt die na initialisatie niet kunnen worden gewijzigd, hoe kunt u ze dan naar elkaar laten verwijzen?
Hoe kunt u onveranderlijkheid in uw eigen code bereiken?
Het alternatief is om 'const'-declaraties te gebruiken in combinatie met de hierboven genoemde technieken voor creatie. Voor het 'muteren' van objecten, gebruikt u de spread-operator, 'Object.assign', 'Array.concat()', enz., om nieuwe objecten te maken in plaats van het originele object te muteren.
Voorbeelden:
// Array voorbeeld
const arr = [1, 2, 3];
const newArr = [...arr, 4]; // [1, 2, 3, 4]
// Object voorbeeld
const human = Object.freeze({ race: 'human' });
const john = { ...human, name: 'John' }; // {race: 'human', name: 'John'}
const alienJohn = { ...john, race: 'alien' }; // {race: 'alien', name: 'John'}