Datensatz- und Tupeltypen
Unveränderlichkeit hält nach und nach Einzug in Javascript. Nachdem Temporal API die Unveränderlichkeit übernommen hat, kommen auch andere unveränderliche Typen, nämlich Records und Tuples, in Javascript zum Einsatz.
Einführung
Beginnen wir mit der Aufzeichnung. Ein Datensatztyp ist eine zutiefst unveränderliche objektähnliche Struktur. Um einen Datensatz zu erstellen, verwenden Sie ein Objektliteral und stellen ihm ein Hash-Symbol (#) voran.
// a classic object
const user = { name: "S. Holmes", age: 27 }
const address = { street: "221B Baker Street", city: 'London'}
// a record
const user = #{ name: "S. Holmes", age: 27 }
const address = #{ street: "221B Baker Street", city: 'London'}
// array
const enemies = ["James Moriarty", "Irene Adler"]
const friends = ["G. Lestrade", "John Hamish Watson"]
// tuple
const enemies = #[ "James Moriarty", "Irene Adler"]
const friends = #["G. Lestrade", "John Hamish Watson"]
#[10, 25] can be viewed as #{0: 10, 1: 25 }
Zusammengesetzte Grundelemente
Die Typen „Records & Tuples“ sind zusammengesetzte Grundelemente mit unveränderlichen Werten . Gut genug, aber was genau bedeutet diese ungeschickte Definition?
Beginnen wir mit dem Begriff Primitive . Bisher enthält Javascript nur sieben primitive Typen :
- Zeichenfolge
- Nummer
- bigint
- Boolescher Wert
- nicht definiert
- Symbol
- Null
// primitive but not compound
const str = "Primitive value"
const number = 1
const bool = true
const n = null
// the compound value:
// consists of two primitive values
const object = { name: 'Primitive value', number: 33 }
// consists of two primitive values & one function
const object = { name: 'Primitive value', number: 33, fn: () => {} }
// record with a nested record
const record = #{ name: 'Primitive value', number: 33,
nestedRecord : #{ number: 50} }
// tuple with a nested tuple and record
const tuple = #[ 'Primitive value', 33, #{ number: 50}, #[1, true, null] ]
// FORBIDDEN usage:
// record with a nested object
// it throws an error as it’s not a primitive value anymore
const record = #{ name: 'Primitive value', number: 33,
nestedObject: { number: 50} }
Record & Tuple ist kein neues Konzept. Die meisten funktionalen Sprachen verfügen über solche Typen. Darüber hinaus haben sie kürzlich auch mehrere gängige Sprachen wie C#, Python und Java übernommen. Selbst in Javascript wurden bereits Arbeiten an unveränderlichen Datenstrukturen durchgeführt, die von der Immutable.JS- Bibliothek und anderen bereitgestellt werden.
Vergleichen wir die Datensätze von immutable.JS mit der Syntax des neuen kommenden nativen Datensatzes:
//Immutable.JS
const record1 = Record({ a: 1, b: 2 })
const record2 = record1.set('b', 50)
const b = record2.get('b')
const recordA = Record({ a: 1, b: 2 })
const recordB = Record({ a: 1, b: 2 })
recordA.equals(recordB) // true
recordA == (recordB) // false !!!
// Native records
const recordNative = #{ a: 1, b: 2 }
const recordNative2 = #{ ...recordNative, b: 50 }
const b = recordNative2.b
const recordA = #{ a: 1, b: 2 }
const recordB = #{ a: 1, b: 2 }
recordA == (recordB) // true !!!
Verstehen Sie mich bitte nicht falsch, ich kritisiere nicht die Arbeitsweise von Immutable.JS. Gar nicht. Immutable.JS hat großartige Arbeit bei der Popularisierung des Konzepts unveränderlicher Typen in Javascript geleistet. Dennoch denke ich, dass es an der Zeit ist, sich darauf zu freuen, die nativen unveränderlichen Typen in unserem Werkzeuggürtel zu haben .
Hauptmerkmale von Record & Tuple
Schauen wir uns nun die Hauptmerkmale der neuen Typen an.
Gleichwertigkeit
Gleichberechtigung war schon immer ein heikles Thema. Bei Verwendung klassischer Objekte müssen Sie sich ausschließlich auf referenzielle Gleichheit verlassen:
// Identity equality
const user = { name: 'Dr. Watson'}
const userRef = user
userRef == user // => true
const user = { name: 'Dr. Watson'}
const user2 = { name: 'Dr. Watson'}
user2 == user // => false
// Value equality
const user = #{ name: 'Dr. Watson'}
const user2 = #{ name: 'Dr. Watson'}
user2 == user // => true
const user1 = #{ name: 'S. Holmes', age: 27 }
const user2 = #{ age: 27, name: 'S. Holmes'}
user1 == user2 // => true
Bei Tupeln ist die Situation etwas anders. Dieser Code funktioniert wie erwartet:
const friends1 = #['G. Lestrade', 'John Hamish Watson']
const friends2 = #['G. Lestrade', 'John Hamish Watson']
friends1 == friends2 // => true
const friends1 = #['G. Lestrade', 'John Hamish Watson']
const friends2 = #['John Hamish Watson', 'G. Lestrade']
friends1 == friends2 // => false
Unveränderlichkeit
Unveränderliche Strukturen haben gegenüber ihren veränderlichen Gegenstücken viele Vorteile. Im Javascript-Kontext helfen uns die unveränderlichen Strukturen, versehentliche Änderungen zu verhindern und unsere Codebasis einfacher zu verstehen.
Ein Beispiel für Unveränderlichkeit:
const primitive = 'string is an immutable primitive'
const primitive2 = primitive + '— change it'
primitive == primitive2 // => false
const obj1 = { id: 'A', value: 1}
const obj2 = obj1
// the following code changes the value of obj2.value
// but it also change obj1.value
obj2.value = 2
obj1 === obj2 // => still true
obj1.value === obj2.value // => true
// But with immutability, you can't change the record like a normal object.
const record1 = #{ id: 'A', value: 1}
record1.value = 2 // throws error
// You need to be more explicit to get changed value
const record2 = #{ ... record1, value: 2}
Arbeiten mit Record & Tuple
Bisher habe ich Ihnen die Syntax zum Erstellen literaler Tupel oder Datensätze gezeigt. Die neue Syntax unterstützt jedoch fortgeschrittenere Anwendungsfälle.
Erstellen Sie eine geänderte Instanz mit Spread
Sie können Ihre Instanzen mit einem bekannten Spread-Operator „…“ ändern. Sie ähnelt der with- Methode für temporale Objekte.
Beispiel für einen Spread-Operator:
const user = #{ name: 'S. Holmes', age: 27 }
// with immutable types, you are restricted to changing value directly:
user.age = 28 // throws error
// With spread operator, you can copy a record
// and modify it at the same time:
const userOneYearOlder = #{ ...user, age: 28}
// => #{ name: 'S. Holmes', get: 28 }
// Of course, it also means:
user == userOneYearOlder // => false
// Tuple
const friends1 = #['G. Lestrade', 'Dr. Watson']
const friends2 = #[...friends1, 'Mrs Hudson']
// => #['G. Lestrade', 'Dr. Watson', 'Mrs Hudson']
// You can modify an item at a particular position with the method 'with'
const friends3 = friends1.with(0, 'Mrs Hudson')
// => #['Mrs Hudson', 'Dr. Watson']
const friends1 = #['Dr. Watson', 'G. Lestrade']
const friends2 = friends1.toReversed()
// => ['G. Lestrade', 'Dr. Watson']
const friends3 = #['G. Lestrade', 'Dr. Watson']
const friends4 = friends3.toSorted()
// friends4 == #['Dr. Watson', 'G. Lestrade']
Konvertieren von Arrays in Tupeln und Objekten in Datensätze
Es ist einfach, Instanzen zwischen unveränderlichen Typen und ihren Gegenstücken zu konvertieren:
const record = #{ name: 'S. Holmes', age: 27 }
// Note that any iterable of entries will work
const record2 = Record.fromEntries([['age', 27], #['name', 'S. Holmes']])
const tuple = Tuple(...[1, 2, 3])
// Note that an iterable will work as well
const tuple2 = Tuple.from([1, 2, 3])
record == #{ name: 'S. Holmes', age: 27 } // => true
tuple == #[1, 2, 3] // => true
// JS Compiler throws an error when using a non-primitive value
// TypeError: cannot use an object as a value in a record
Record({ a: {} })
// TypeError: cannot use an object as a value in a record
Tuple.from([{}, {} , {}])
const obj = { ...record }
const arr = [ ...tuple ]
Sollten wir Record & Tuple übernehmen?
Ich würde ohne zu zögern ja sagen. Ich habe meine Argumente unten zusammengefasst.
Vertrautheit
Mir gefällt die Idee aus dem Buch „Kill with it Fire“ von Marriane Bellotti:
Die Einführung neuer Technologien wird durch Vertrautheit und nicht durch Überlegenheit vorangetrieben.
Marrianes Konzept passt hier gut. Die Autoren des Tuple & Record-Vorschlags haben versucht, die Semantik von Objekt und Array nachzuahmen, um uns bei der Übernahme dieser neuen primitiven Typen zu helfen. Dadurch verhält sich der Datensatz ähnlich wie Objekte oder Arrays. Daher sollte die Einführung schnell erfolgen.
Modellierung unserer Geschäftsdomäne
Die Unveränderlichkeit und der Vergleich kommen bei der Modellierung der Domäne zum Tragen. Entwickler von Geschäftsanwendungen verwenden normalerweise zahlreiche Domänenklassen, die Benutzer, Rechnungen, Berichte und andere reale Objekte darstellen. In diesen Fällen können Aufzeichnungen von Nutzen sein.
Aber bevor die OOP-Fanatiker ihre Mistgabeln in die Hand nehmen, möchte ich klarstellen: Ich weiß, dass die Verwendung von Datensätzen für Domänenklassen zu einem anämischen Domänenmodell führen sollte – OOP-Anti-Pattern. Dennoch ist es auch möglich, Verhaltensweisen für Domänenkonzepte in einer Programmiersprache mithilfe von Funktionen höherer Ordnung oder anderen funktionalen Techniken zu definieren. Lesen Sie gerne mehr über diese Idee in „ Sind funktionale Domänenmodelle immer anämisch “ oder „ Das anämische Domänenmodell ist kein Anti-Pattern, es ist ein SOLID-Design.“ ”
Letztendlich habe ich sowohl das klassische als auch das anämische Domänenmodell ausprobiert, und das erstere passte besser zu mir, selbst in der strengen OOP-Sprache.
Was ist mit Tupeln?
Während Datensätze für die Modellierung Ihrer Domänenklassen geeignet sind, werden Tupel spezieller verwendet. Tupel bieten eine einfache Möglichkeit, mehrere Werte von oder an eine Funktion zurückzugeben oder zu übergeben. Javascript-Entwickler, insbesondere React-Entwickler, haben Tupel häufig verwendet, nachdem die Dekonstruktionsaufgabe in Javascript gelandet ist.
Der useState- Hook von React hat es perfekt veranschaulicht:
// [count, setCount] is a classic tuple-like array
const [count, setCount] = useState(0)
Ein weiterer Anwendungsfall für Tupel ist das Memoisierungsmuster . Jetzt müssen Sie ohne native Tupel Tricks anwenden, um die Argumente der Funktion zu serialisieren, wie zum Beispiel JSON.stringify :
const memoized = new Map()
function compute(...args) {
// a hack
const key = JSON.stringify({...args})
if (memoized.has(key))
{
return memoized.get(key)
}
else {
const value = peformSlowComputation(...args)
memoized[key] = value
return value
}
}
const memoized = new Map()
function compute(... args) {
// a nice and clean key
const key = #[...args]
if (memoized.has(key ))
{
return memoized.get(key)
}
else {
const value = performSlowComputation(...args)
memoized[key] = value
return value
}
}
Ich muss Sie auch warnen: Gehen Sie bitte vorsichtig mit Tupeln um. Manchmal sieht Ihr Anwendungsfall für die Verwendung eines Tupels völlig in Ordnung aus. Letztendlich könnte es jedoch zu mehr Verwirrung führen. Zum Beispiel geografische Koordinaten:
const [lat, long] = getCoordinates()
const [long, lat] = getCoordinates()
// it works in both cases:
const {lat,long} = getCoordinates()
const {long,lat} = getCoordinates()
Optimierung
Unveränderliche Strukturen sorgen nicht nur für saubereren Code, sondern haben auch versteckte Auswirkungen auf die Leistung. Da JS eine dynamische Sprache ist, muss der JS-Compiler viele Vermutungen über das Speicherlayout des Objekts anstellen. Die Tatsache, dass Sie das Feld eines neuen Objekts jederzeit hinzufügen können, erschwert die Sache. Statische Sprachen bieten eine bessere Optimierung, da sie die Form der Objekte im Voraus kennen. Um mehr zu erfahren, lesen Sie den folgenden Artikel zur Funktionsoptimierung in V8 . Bei unveränderlichen Typen können Autoren von Javascript-Compilern diese möglicherweise ähnlich wie bei der statischen Typisierung optimieren.
Status
Im Januar 2023 befinden sich Record und Tuple noch in Phase 2, was bedeutet, dass sie nicht so schnell in unseren Browsern landen werden. Doch mit diesem funktionierenden Polyfill- und Babel-Plugin können Sie beginnen, mögliche Anwendungsfälle zu erkunden. Oder Sie spielen mit diesen neuen Typen auf diesem Spielplatz .
Abschluss
Record & Tuple wird eine weitere große Sache in Javascript sein. Zugegebenermaßen ist es auf den ersten Blick weniger nützlich als die neue Temporal API oder eine schicke API wie WebXR . Dennoch bringt es ein weiteres beliebtes Konzept aus funktionalen Sprachen mit, das unsere Codierung definitiv erleichtern wird.
Wir sind ACTUM Digital und dieser Artikel wurde von Marek geschrieben , Senior Front End Developer der Apollo Division.
Wenn Sie Hilfe bei Ihrem Projekt oder Ihrer Initiative suchen, schreiben Sie uns einfach eine Nachricht.

![Was ist überhaupt eine verknüpfte Liste? [Teil 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































