19. Maps und Sets
Inhaltsverzeichnis
Bitte unterstützen Sie dieses Buch: kaufen Sie es (PDF, EPUB, MOBI) oder spenden Sie
(Werbung, bitte nicht blockieren.)

19. Maps und Sets



19.1 Überblick

Unter anderem sind die folgenden vier Datenstrukturen neu in ECMAScript 6: Map, WeakMap, Set und WeakSet.

19.1.1 Maps

Die Schlüssel einer Map können beliebige Werte sein

> const map = new Map(); // create an empty Map
> const KEY = {};

> map.set(KEY, 123);
> map.get(KEY)
123
> map.has(KEY)
true
> map.delete(KEY);
true
> map.has(KEY)
false

Sie können ein Array (oder jedes andere iterierbare Objekt) mit [Schlüssel, Wert]-Paaren verwenden, um die anfänglichen Daten in der Map einzurichten

const map = new Map([
    [ 1, 'one' ],
    [ 2, 'two' ],
    [ 3, 'three' ], // trailing comma is ignored
]);

19.1.2 Sets

Ein Set ist eine Sammlung eindeutiger Elemente

const arr = [5, 1, 5, 7, 7, 5];
const unique = [...new Set(arr)]; // [ 5, 1, 7 ]

Wie Sie sehen, können Sie ein Set mit Elementen initialisieren, wenn Sie dem Konstruktor ein iterierbares Objekt (arr im Beispiel) dieser Elemente übergeben.

19.1.3 WeakMaps

Eine WeakMap ist eine Map, die ihre Schlüssel nicht vor der Garbage Collection schützt. Das bedeutet, dass Sie Daten mit Objekten verknüpfen können, ohne sich um Speicherlecks kümmern zu müssen. Zum Beispiel

//----- Manage listeners

const _objToListeners = new WeakMap();

function addListener(obj, listener) {
    if (! _objToListeners.has(obj)) {
        _objToListeners.set(obj, new Set());
    }
    _objToListeners.get(obj).add(listener);
}

function triggerListeners(obj) {
    const listeners = _objToListeners.get(obj);
    if (listeners) {
        for (const listener of listeners) {
            listener();
        }
    }
}

//----- Example: attach listeners to an object

const obj = {};
addListener(obj, () => console.log('hello'));
addListener(obj, () => console.log('world'));

//----- Example: trigger listeners

triggerListeners(obj);

// Output:
// hello
// world

19.2 Map

JavaScript hatte schon immer eine sehr spartanische Standardbibliothek. Eine stark vermisste Datenstruktur war die für die Zuordnung von Werten zu Werten. Das Beste, was man in ECMAScript 5 bekommen konnte, war eine Map von Strings zu beliebigen Werten, indem man Objekte missbrauchte. Selbst dann gibt es mehrere Fallstricke, die einen stolpern lassen können.

Die Datenstruktur Map in ECMAScript 6 erlaubt die Verwendung beliebiger Werte als Schlüssel und ist sehr willkommen.

19.2.1 Grundlegende Operationen

Arbeiten mit einzelnen Einträgen

> const map = new Map();

> map.set('foo', 123);
> map.get('foo')
123

> map.has('foo')
true
> map.delete('foo')
true
> map.has('foo')
false

Ermitteln der Größe einer Map und Leeren

> const map = new Map();
> map.set('foo', true);
> map.set('bar', false);

> map.size
2
> map.clear();
> map.size
0

19.2.2 Einrichten einer Map

Sie können eine Map über ein iterierbares Objekt mit Schlüssel-Wert-"Paaren" (Arrays mit 2 Elementen) einrichten. Eine Möglichkeit ist die Verwendung eines Arrays (das iterierbar ist)

const map = new Map([
    [ 1, 'one' ],
    [ 2, 'two' ],
    [ 3, 'three' ], // trailing comma is ignored
]);

Alternativ ist die Methode set() verkettbar

const map = new Map()
.set(1, 'one')
.set(2, 'two')
.set(3, 'three');

19.2.3 Schlüssel

Jeder Wert kann ein Schlüssel sein, sogar ein Objekt

const map = new Map();

const KEY1 = {};
map.set(KEY1, 'hello');
console.log(map.get(KEY1)); // hello

const KEY2 = {};
map.set(KEY2, 'world');
console.log(map.get(KEY2)); // world
19.2.3.1 Welche Schlüssel gelten als gleich?

Die meisten Map-Operationen müssen prüfen, ob ein Wert gleich einem der Schlüssel ist. Dies geschieht über die interne Operation SameValueZero, die wie === funktioniert, aber NaN als sich selbst gleich betrachtet.

Sehen wir uns zuerst an, wie === mit NaN umgeht

> NaN === NaN
false

Umgekehrt können Sie NaN als Schlüssel in Maps verwenden, genau wie jeden anderen Wert

> const map = new Map();

> map.set(NaN, 123);
> map.get(NaN)
123

Wie === werden auch -0 und +0 als derselbe Wert betrachtet. Das ist normalerweise der beste Weg, um mit den beiden Nullen umzugehen (Details werden in "Speaking JavaScript" erklärt).

> map.set(-0, 123);
> map.get(+0)
123

Unterschiedliche Objekte werden immer als unterschiedlich betrachtet. Das lässt sich (noch) nicht konfigurieren, wie später im FAQ erklärt.

> new Map().set({}, 1).set({}, 2).size
2

Das Abrufen eines unbekannten Schlüssels ergibt undefined

> new Map().get('asfddfsasadf')
undefined

19.2.4 Iterieren über Maps

Richten wir eine Map ein, um zu demonstrieren, wie man darüber iterieren kann.

const map = new Map([
    [false, 'no'],
    [true,  'yes'],
]);

Maps zeichnen die Reihenfolge auf, in der Elemente eingefügt werden, und halten diese Reihenfolge bei der Iteration über Schlüssel, Werte oder Einträge ein.

19.2.4.1 Iterierbare Objekte für Schlüssel und Werte

keys() gibt ein iterierbares Objekt über die Schlüssel in der Map zurück

for (const key of map.keys()) {
    console.log(key);
}
// Output:
// false
// true

values() gibt ein iterierbares Objekt über die Werte in der Map zurück

for (const value of map.values()) {
    console.log(value);
}
// Output:
// no
// yes
19.2.4.2 Iterierbare Objekte für Einträge

entries() gibt die Einträge der Map als iterierbares Objekt über [Schlüssel, Wert]-Paare (Arrays) zurück.

for (const entry of map.entries()) {
    console.log(entry[0], entry[1]);
}
// Output:
// false no
// true yes

Destrukturierung ermöglicht es Ihnen, direkt auf Schlüssel und Werte zuzugreifen

for (const [key, value] of map.entries()) {
    console.log(key, value);
}

Die Standardmethode zur Iteration über eine Map ist entries()

> map[Symbol.iterator] === map.entries
true

Somit können Sie den vorherigen Code-Schnipsel noch kürzer gestalten

for (const [key, value] of map) {
    console.log(key, value);
}
19.2.4.3 Konvertieren von iterierbaren Objekten (inkl. Maps) in Arrays

Der Spread-Operator (...) kann ein iterierbares Objekt in ein Array umwandeln. Das ermöglicht uns, das Ergebnis von Map.prototype.keys() (ein iterierbares Objekt) in ein Array zu konvertieren

> const map = new Map().set(false, 'no').set(true, 'yes');
> [...map.keys()]
[ false, true ]

Maps sind ebenfalls iterierbar, was bedeutet, dass der Spread-Operator Maps in Arrays umwandeln kann

> const map = new Map().set(false, 'no').set(true, 'yes');
> [...map]
[ [ false, 'no' ],
  [ true, 'yes' ] ]

19.2.5 Schleifen über Map-Einträge

Die Methode Map.prototype.forEach hat folgende Signatur

Map.prototype.forEach((value, key, map) => void, thisArg?) : void

Die Signatur des ersten Parameters spiegelt die Signatur des Callbacks von Array.prototype.forEach wider, weshalb der Wert zuerst kommt.

const map = new Map([
    [false, 'no'],
    [true,  'yes'],
]);
map.forEach((value, key) => {
    console.log(key, value);
});
// Output:
// false no
// true yes

19.2.6 Mappen und Filtern von Maps

Sie können Arrays map() und filter(), aber es gibt keine solchen Operationen für Maps. Die Lösung ist

  1. Konvertieren Sie die Map in ein Array von [Schlüssel, Wert]-Paaren.
  2. Mappen oder filtern Sie das Array.
  3. Konvertieren Sie das Ergebnis zurück in eine Map.

Ich werde die folgende Map verwenden, um zu demonstrieren, wie das funktioniert.

const originalMap = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');

Mappen von originalMap

const mappedMap = new Map( // step 3
    [...originalMap] // step 1
    .map(([k, v]) => [k * 2, '_' + v]) // step 2
);
// Resulting Map: {2 => '_a', 4 => '_b', 6 => '_c'}

Filtern von originalMap

const filteredMap = new Map( // step 3
    [...originalMap] // step 1
    .filter(([k, v]) => k < 3) // step 2
);
// Resulting Map: {1 => 'a', 2 => 'b'}

Schritt 1 wird durch den Spread-Operator (...) durchgeführt, den ich bereits erklärt habe.

19.2.7 Kombinieren von Maps

Es gibt keine Methoden zum Kombinieren von Maps, weshalb der Ansatz aus dem vorherigen Abschnitt verwendet werden muss, um dies zu tun.

Lassen Sie uns die folgenden zwei Maps kombinieren

const map1 = new Map()
.set(1, 'a1')
.set(2, 'b1')
.set(3, 'c1');

const map2 = new Map()
.set(2, 'b2')
.set(3, 'c2')
.set(4, 'd2');

Um map1 und map2 zu kombinieren, wandle ich sie über den Spread-Operator (...) in Arrays um und verknüpfe diese Arrays. Danach konvertiere ich das Ergebnis zurück in eine Map. Das alles geschieht in der ersten Zeile.

> const combinedMap = new Map([...map1, ...map2])
> [...combinedMap] // convert to Array to display
[ [ 1, 'a1' ],
  [ 2, 'b2' ],
  [ 3, 'c2' ],
  [ 4, 'd2' ] ]

19.2.8 Beliebige Maps als JSON über Arrays von Paaren

Wenn eine Map beliebige (JSON-kompatible) Daten enthält, können wir sie in JSON konvertieren, indem wir sie als Array von Schlüssel-Wert-Paaren (2-elementige Arrays) kodieren. Untersuchen wir zunächst, wie diese Kodierung erreicht wird.

19.2.8.1 Konvertieren von Maps zu und von Arrays von Paaren

Der Spread-Operator ermöglicht es Ihnen, eine Map in ein Array von Paaren zu konvertieren

> const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
> [...myMap]
[ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]

Der Map-Konstruktor ermöglicht es Ihnen, ein Array von Paaren in eine Map zu konvertieren

> new Map([[true, 7], [{foo: 3}, ['abc']]])
Map {true => 7, Object {foo: 3} => ['abc']}
19.2.8.2 Die Konvertierung zu und von JSON

Nutzen wir dieses Wissen, um jede Map mit JSON-kompatiblen Daten in JSON zu konvertieren und zurück

function mapToJson(map) {
    return JSON.stringify([...map]);
}
function jsonToMap(jsonStr) {
    return new Map(JSON.parse(jsonStr));
}

Die folgende Interaktion zeigt, wie diese Funktionen verwendet werden

> const myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);

> mapToJson(myMap)
'[[true,7],[{"foo":3},["abc"]]]'

> jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
Map {true => 7, Object {foo: 3} => ['abc']}

19.2.9 String-Maps als JSON über Objekte

Wenn eine Map nur Strings als Schlüssel hat, können Sie sie in JSON konvertieren, indem Sie sie als Objekt kodieren. Untersuchen wir zunächst, wie diese Kodierung erreicht wird.

19.2.9.1 Konvertieren einer String-Map zu und von einem Objekt

Die folgenden beiden Funktionen konvertieren String-Maps zu und von Objekten

function strMapToObj(strMap) {
    const obj = Object.create(null);
    for (const [k,v] of strMap) {
        // We don’t escape the key '__proto__'
        // which can cause problems on older engines
        obj[k] = v;
    }
    return obj;
}
function objToStrMap(obj) {
    const strMap = new Map();
    for (const k of Object.keys(obj)) {
        strMap.set(k, obj[k]);
    }
    return strMap;
}

Lassen Sie uns diese beiden Funktionen verwenden

> const myMap = new Map().set('yes', true).set('no', false);

> strMapToObj(myMap)
{ yes: true, no: false }

> objToStrMap({yes: true, no: false})
[ [ 'yes', true ], [ 'no', false ] ]
19.2.9.2 Die Konvertierung zu und von JSON

Mit diesen Hilfsfunktionen funktioniert die Konvertierung zu JSON wie folgt

function strMapToJson(strMap) {
    return JSON.stringify(strMapToObj(strMap));
}
function jsonToStrMap(jsonStr) {
    return objToStrMap(JSON.parse(jsonStr));
}

Dies ist ein Beispiel für die Verwendung dieser Funktionen

> const myMap = new Map().set('yes', true).set('no', false);

> strMapToJson(myMap)
'{"yes":true,"no":false}'

> jsonToStrMap('{"yes":true,"no":false}');
Map {'yes' => true, 'no' => false}

19.2.10 Map API

Konstruktor

Handhabung einzelner Einträge

Handhabung aller Einträge

Iterieren und Schleifen: geschieht in der Reihenfolge, in der Einträge zu einer Map hinzugefügt wurden.

19.3 WeakMap

WeakMaps funktionieren größtenteils wie Maps, mit den folgenden Unterschieden

Die folgenden Abschnitte erklären jeden dieser Unterschiede.

19.3.1 WeakMap-Schlüssel sind Objekte

Wenn Sie einen Eintrag zu einer WeakMap hinzufügen, muss der Schlüssel ein Objekt sein

const wm = new WeakMap()

wm.set('abc', 123); // TypeError
wm.set({}, 123); // OK

19.3.2 WeakMap-Schlüssel werden schwach gehalten

Die Schlüssel in einer WeakMap werden *schwach gehalten*: Normalerweise kann ein Objekt, auf das von keiner Speicherstelle (Variable, Eigenschaft usw.) verwiesen wird, vom Garbage Collector eingesammelt werden. WeakMap-Schlüssel zählen in diesem Sinne nicht als Speicherstellen. Anders ausgedrückt: Ein Objekt, das ein Schlüssel in einer WeakMap ist, verhindert nicht, dass das Objekt vom Garbage Collector eingesammelt wird.

Zusätzlich verschwindet, sobald ein Schlüssel nicht mehr existiert, auch sein Eintrag (schließlich, aber es gibt ohnehin keine Möglichkeit, dies zu erkennen).

19.3.3 Man kann sich keinen Überblick über eine WeakMap verschaffen oder sie leeren

Es ist unmöglich, die inneren Bestandteile einer WeakMap zu inspizieren, um sich einen Überblick zu verschaffen. Dazu gehört auch die Unmöglichkeit, über Schlüssel, Werte oder Einträge zu iterieren. Anders ausgedrückt: Um Inhalte aus einer WeakMap abzurufen, benötigen Sie einen Schlüssel. Es gibt auch keine Möglichkeit, eine WeakMap zu leeren (als Workaround können Sie eine völlig neue Instanz erstellen).

Diese Einschränkungen ermöglichen eine Sicherheitseigenschaft. Zitat von Mark Miller: "Die Zuordnung von WeakMap/Schlüssel-Paar-Wert kann nur von jemandem beobachtet oder beeinflusst werden, der sowohl die WeakMap als auch den Schlüssel besitzt. Mit clear() wäre jemand, der nur die WeakMap besitzt, in der Lage gewesen, die Zuordnung von WeakMap und Schlüssel zu Wert zu beeinflussen."

Zusätzlich wäre die Iteration schwer zu implementieren, da man garantieren müsste, dass Schlüssel schwach gehalten bleiben.

19.3.4 Anwendungsfälle für WeakMaps

WeakMaps sind nützlich, um Daten mit Objekten zu verknüpfen, deren Lebenszyklus man nicht kontrollieren kann (oder will). In diesem Abschnitt betrachten wir zwei Beispiele

19.3.4.1 Zwischenspeichern berechneter Ergebnisse über WeakMaps

Mit WeakMaps können Sie bereits berechnete Ergebnisse mit Objekten verknüpfen, ohne sich um die Speicherverwaltung kümmern zu müssen. Die folgende Funktion countOwnKeys ist ein Beispiel: Sie speichert frühere Ergebnisse in der WeakMap cache.

const cache = new WeakMap();
function countOwnKeys(obj) {
    if (cache.has(obj)) {
        console.log('Cached');
        return cache.get(obj);
    } else {
        console.log('Computed');
        const count = Object.keys(obj).length;
        cache.set(obj, count);
        return count;
    }
}

Wenn wir diese Funktion mit einem Objekt obj verwenden, sehen Sie, dass das Ergebnis nur für den ersten Aufruf berechnet wird, während für den zweiten Aufruf ein zwischengespeicherter Wert verwendet wird

> const obj = { foo: 1, bar: 2};
> countOwnKeys(obj)
Computed
2
> countOwnKeys(obj)
Cached
2
19.3.4.2 Verwalten von Listenern

Nehmen wir an, wir möchten Listener an Objekte anhängen, ohne die Objekte zu ändern. Sie könnten Listener zu einem Objekt obj hinzufügen

const obj = {};
addListener(obj, () => console.log('hello'));
addListener(obj, () => console.log('world'));

Und Sie könnten die Listener auslösen

triggerListeners(obj);

// Output:
// hello
// world

Die beiden Funktionen addListener() und triggerListeners() können wie folgt implementiert werden.

const _objToListeners = new WeakMap();

function addListener(obj, listener) {
    if (! _objToListeners.has(obj)) {
        _objToListeners.set(obj, new Set());
    }
    _objToListeners.get(obj).add(listener);
}

function triggerListeners(obj) {
    const listeners = _objToListeners.get(obj);
    if (listeners) {
        for (const listener of listeners) {
            listener();
        }
    }
}

Der Vorteil der Verwendung einer WeakMap hier ist, dass, sobald ein Objekt vom Garbage Collector eingesammelt wird, auch seine Listener eingesammelt werden. Anders ausgedrückt: Es wird keine Speicherlecks geben.

19.3.4.3 Verwalten privater Daten über WeakMaps

Im folgenden Code werden die WeakMaps _counter und _action verwendet, um die Daten von virtuellen Eigenschaften von Instanzen von Countdown zu speichern

const _counter = new WeakMap();
const _action = new WeakMap();
class Countdown {
    constructor(counter, action) {
        _counter.set(this, counter);
        _action.set(this, action);
    }
    dec() {
        let counter = _counter.get(this);
        if (counter < 1) return;
        counter--;
        _counter.set(this, counter);
        if (counter === 0) {
            _action.get(this)();
        }
    }
}

Weitere Informationen zu dieser Technik finden Sie im Kapitel über Klassen.

19.3.5 WeakMap API

Der Konstruktor und die vier Methoden von WeakMap funktionieren genauso wie ihre Map-Äquivalente

new WeakMap(entries? : Iterable<[any,any]>)

WeakMap.prototype.get(key) : any
WeakMap.prototype.set(key, value) : this
WeakMap.prototype.has(key) : boolean
WeakMap.prototype.delete(key) : boolean

19.4 Set

ECMAScript 5 hat auch keine Set-Datenstruktur. Es gibt zwei mögliche Workarounds

ECMAScript 6 hat die Datenstruktur Set, die für beliebige Werte funktioniert, schnell ist und NaN korrekt behandelt.

19.4.1 Grundlegende Operationen

Verwaltung einzelner Elemente

> const set = new Set();
> set.add('red')

> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false

Ermitteln der Größe eines Sets und Leeren

> const set = new Set();
> set.add('red')
> set.add('green')

> set.size
2
> set.clear();
> set.size
0

19.4.2 Einrichten eines Sets

Sie können ein Set über ein iterierbares Objekt der Elemente, aus denen das Set besteht, einrichten. Zum Beispiel über ein Array

const set = new Set(['red', 'green', 'blue']);

Alternativ ist die Methode add verkettbar

const set = new Set().add('red').add('green').add('blue');
19.4.2.1 Fallstrick: new Set() hat höchstens ein Argument

Der Set-Konstruktor hat null oder ein Argument

Weitere Argumente werden ignoriert, was zu unerwarteten Ergebnissen führen kann

> Array.from(new Set(['foo', 'bar']))
[ 'foo', 'bar' ]
> Array.from(new Set('foo', 'bar'))
[ 'f', 'o' ]

Für das zweite Set wird nur 'foo' verwendet (das iterierbar ist), um das Set zu definieren.

19.4.3 Vergleichen von Set-Elementen

Wie bei Maps werden Elemente ähnlich wie bei === verglichen, mit der Ausnahme, dass NaN wie jeder andere Wert behandelt wird.

> const set = new Set([NaN]);
> set.size
1
> set.has(NaN)
true

Das Hinzufügen eines Elements ein zweites Mal hat keine Auswirkungen

> const set = new Set();

> set.add('foo');
> set.size
1

> set.add('foo');
> set.size
1

Ähnlich wie bei === werden zwei verschiedene Objekte niemals als gleich betrachtet (was derzeit nicht angepasst werden kann, wie später im FAQ erklärt, später)

> const set = new Set();

> set.add({});
> set.size
1

> set.add({});
> set.size
2

19.4.4 Iterieren

Sets sind iterierbar und die for-of-Schleife funktioniert wie erwartet

const set = new Set(['red', 'green', 'blue']);
for (const x of set) {
    console.log(x);
}
// Output:
// red
// green
// blue

Wie Sie sehen, behalten Sets die Iterationsreihenfolge bei. Das heißt, Elemente werden immer in der Reihenfolge übergeben, in der sie eingefügt wurden.

Der bereits erklärte Spread-Operator (...) funktioniert mit iterierbaren Objekten und ermöglicht Ihnen somit, ein Set in ein Array zu konvertieren

const set = new Set(['red', 'green', 'blue']);
const arr = [...set]; // ['red', 'green', 'blue']

Wir haben nun eine prägnante Möglichkeit, ein Array in ein Set und zurück zu konvertieren, was zur Folge hat, dass Duplikate aus dem Array entfernt werden

const arr = [3, 5, 2, 2, 5, 5];
const unique = [...new Set(arr)]; // [3, 5, 2]

19.4.5 Mappen und Filtern

Im Gegensatz zu Arrays haben Sets nicht die Methoden map() und filter(). Ein Workaround ist, sie in Arrays zu konvertieren und zurück.

Mappen

const set = new Set([1, 2, 3]);
set = new Set([...set].map(x => x * 2));
// Resulting Set: {2, 4, 6}

Filtern

const set = new Set([1, 2, 3, 4, 5]);
set = new Set([...set].filter(x => (x % 2) == 0));
// Resulting Set: {2, 4}

19.4.6 Vereinigung, Schnittmenge, Differenz

ECMAScript 6 Sets haben keine Methoden zur Berechnung der Vereinigung (z. B. addAll), Schnittmenge (z. B. retainAll) oder Differenz (z. B. removeAll). Dieser Abschnitt erklärt, wie diese Einschränkung umgangen werden kann.

19.4.6.1 Vereinigung

Vereinigung (ab): Erstellen Sie ein Set, das die Elemente von Set a und Set b enthält.

const a = new Set([1,2,3]);
const b = new Set([4,3,2]);
const union = new Set([...a, ...b]);
    // {1,2,3,4}

Das Muster ist immer dasselbe

Der Spread-Operator (...) fügt die Elemente von etwas Iterierbarem (wie einem Set) in ein Array ein. Daher bedeutet [...a, ...b], dass a und b in Arrays konvertiert und verkettet werden. Es ist äquivalent zu [...a].concat([...b]).

19.4.6.2 Schnittmenge

Schnittmenge (ab): Erstellen Sie ein Set, das die Elemente von Set a enthält, die auch in Set b vorhanden sind.

const a = new Set([1,2,3]);
const b = new Set([4,3,2]);
const intersection = new Set(
    [...a].filter(x => b.has(x)));
    // {2,3}

Schritte: Konvertieren Sie a in ein Array, filtern Sie die Elemente, konvertieren Sie das Ergebnis in ein Set.

19.4.6.3 Differenz

Differenz (a \ b): Erstellen Sie ein Set, das die Elemente von Set a enthält, die nicht in Set b sind. Diese Operation wird auch manchmal als *Minus* (-) bezeichnet.

const a = new Set([1,2,3]);
const b = new Set([4,3,2]);
const difference = new Set(
    [...a].filter(x => !b.has(x)));
    // {1}

19.4.7 Set API

Konstruktor

Einzelne Set-Elemente

Alle Set-Elemente

Iterieren und Schleifen

Symmetrie mit Map: Die folgenden beiden Methoden existieren nur, damit die Schnittstelle von Sets der Schnittstelle von Maps ähnelt. Jedes Set-Element wird behandelt, als wäre es ein Map-Eintrag, dessen Schlüssel und Wert das Element sind.

entries() ermöglicht es Ihnen, ein Set in eine Map zu konvertieren

const set = new Set(['a', 'b', 'c']);
const map = new Map(set.entries());
    // Map { 'a' => 'a', 'b' => 'b', 'c' => 'c' }

19.5 WeakSet

Ein WeakSet ist ein Set, das seine Elemente nicht vor der Garbage Collection schützt. Konsultieren Sie den Abschnitt über WeakMap für eine Erklärung, warum WeakSets keine Iteration, Schleifen und kein Leeren zulassen.

19.5.1 Anwendungsfälle für WeakSets

Da man nicht über ihre Elemente iterieren kann, gibt es nicht sehr viele Anwendungsfälle für WeakSets. Sie ermöglichen es jedoch, Objekte zu markieren.

19.5.1.1 Markieren von Objekten, die von einer Factory-Funktion erstellt wurden

Wenn Sie beispielsweise eine Factory-Funktion für Proxies haben, können Sie ein WeakSet verwenden, um aufzuzeichnen, welche Objekte von dieser Factory erstellt wurden

const _proxies = new WeakSet();

function createProxy(obj) {
    const proxy = ···;
    _proxies.add(proxy);
    return proxy;
}

function isProxy(obj) {
    return _proxies.has(obj);
}

Das vollständige Beispiel ist im Kapitel über Proxies zu sehen.

_proxies muss ein WeakSet sein, da ein normales Set verhindern würde, dass ein Proxy vom Garbage Collector eingesammelt wird, sobald nicht mehr darauf verwiesen wird.

19.5.1.2 Markieren von Objekten als sicher für die Verwendung mit einer Methode

Domenic Denicola zeigt, wie eine Klasse Foo sicherstellen kann, dass ihre Methoden nur auf Instanzen angewendet werden, die von ihr erstellt wurden

const foos = new WeakSet();

class Foo {
    constructor() {
        foos.add(this);
    }

    method() {
        if (!foos.has(this)) {
            throw new TypeError('Incompatible object!');
        }
    }
}

19.5.2 WeakSet API

Der Konstruktor und die drei Methoden von WeakSet funktionieren genauso wie ihre Set-Äquivalente

new WeakSet(elements? : Iterable<any>)

WeakSet.prototype.add(value)
WeakSet.prototype.has(value)
WeakSet.prototype.delete(value)

19.6 FAQ: Maps und Sets

19.6.1 Warum haben Maps und Sets die Eigenschaft size und nicht length?

Arrays haben die Eigenschaft length, um die Anzahl der Einträge zu zählen. Maps und Sets haben eine andere Eigenschaft, size.

Der Grund für diesen Unterschied ist, dass length für Sequenzen gedacht ist, Datenstrukturen, die indizierbar sind – wie Arrays. size ist für Sammlungen gedacht, die primär ungeordnet sind – wie Maps und Sets.

19.6.2 Warum kann ich nicht konfigurieren, wie Maps und Sets Schlüssel und Werte vergleichen?

Es wäre schön, wenn es eine Möglichkeit gäbe zu konfigurieren, was Map-Schlüssel und Set-Elemente als gleich betrachtet werden. Aber diese Funktion wurde verschoben, da sie schwierig zu implementieren und effizient zu gestalten ist.

19.6.3 Gibt es eine Möglichkeit, einen Standardwert anzugeben, wenn etwas aus einer Map geholt wird?

Wenn Sie einen Schlüssel verwenden, um etwas aus einer Map zu holen, möchten Sie gelegentlich einen Standardwert angeben, der zurückgegeben wird, wenn der Schlüssel nicht in der Map vorhanden ist. ES6 Maps erlauben dies nicht direkt. Aber Sie können den Or-Operator (||) verwenden, wie im folgenden Code gezeigt. countChars gibt eine Map zurück, die Zeichen auf die Anzahl ihrer Vorkommen abbildet.

function countChars(chars) {
    const charCounts = new Map();
    for (const ch of chars) {
        ch = ch.toLowerCase();
        const prevCount = charCounts.get(ch) || 0; // (A)
        charCounts.set(ch, prevCount+1);
    }
    return charCounts;
}

In Zeile A wird der Standardwert 0 verwendet, wenn ch nicht in charCounts ist und get() undefined zurückgibt.

19.6.4 Wann sollte ich eine Map verwenden, wann ein Objekt?

Wenn Sie etwas anderes als Strings auf beliebige Daten abbilden, haben Sie keine Wahl: Sie müssen eine Map verwenden.

Wenn Sie jedoch Strings auf beliebige Daten abbilden, müssen Sie entscheiden, ob Sie ein Objekt verwenden möchten oder nicht. Eine grobe allgemeine Richtlinie ist

19.6.5 Wann würde ich ein Objekt als Schlüssel in einer Map verwenden?

Map-Schlüssel sind hauptsächlich sinnvoll, wenn sie nach Wert verglichen werden (gleicher "Inhalt" bedeutet, dass zwei Werte als gleich gelten, nicht als identisch). Das schließt Objekte aus. Es gibt einen Anwendungsfall – das externe Anhängen von Daten an Objekte, aber dieser Anwendungsfall wird besser von WeakMaps bedient, bei denen ein Eintrag verschwindet, wenn der Schlüssel verschwindet.

Weiter: 20. Typed Arrays