size und nicht length?Unter anderem sind die folgenden vier Datenstrukturen neu in ECMAScript 6: Map, WeakMap, Set und WeakSet.
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
]);
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.
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
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.
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
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');
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
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
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.
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
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);
}
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' ] ]
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
Sie können Arrays map() und filter(), aber es gibt keine solchen Operationen für Maps. Die Lösung ist
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.
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' ] ]
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.
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']}
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']}
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.
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 ] ]
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}
Konstruktor
new Map(entries? : Iterable<[any,any]>)iterable nicht angeben, wird eine leere Map erstellt. Wenn Sie ein iterierbares Objekt mit Schlüssel-Wert-Paaren angeben, werden diese Paare verwendet, um Einträge zur Map hinzuzufügen. Zum Beispiel const map = new Map([
[ 1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
]);
Handhabung einzelner Einträge
Map.prototype.get(key) : anyvalue zurück, dem der key in dieser Map zugeordnet ist. Wenn es keinen Schlüssel key in dieser Map gibt, wird undefined zurückgegeben.Map.prototype.set(key, value) : thiskey vorhanden ist, wird er aktualisiert. Andernfalls wird ein neuer Eintrag erstellt. Diese Methode gibt this zurück, sodass Sie sie verketten können.Map.prototype.has(key) : booleanMap.prototype.delete(key) : booleankey vorhanden ist, wird er entfernt und true zurückgegeben. Andernfalls geschieht nichts und es wird false zurückgegeben.Handhabung aller Einträge
get Map.prototype.size : numberMap.prototype.clear() : voidIterieren und Schleifen: geschieht in der Reihenfolge, in der Einträge zu einer Map hinzugefügt wurden.
Map.prototype.entries() : Iterable<[any,any]>Map.prototype.forEach((value, key, collection) => void, thisArg?) : voidthisArg angegeben ist, wird this für jede Invokation darauf gesetzt. Andernfalls wird this auf undefined gesetzt.Map.prototype.keys() : Iterable<any>Map.prototype.values() : Iterable<any>Map.prototype[Symbol.iterator]() : Iterable<[any,any]>Map.prototype.entries.WeakMaps funktionieren größtenteils wie Maps, mit den folgenden Unterschieden
Die folgenden Abschnitte erklären jeden dieser Unterschiede.
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
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).
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.
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
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
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.
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.
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
ECMAScript 5 hat auch keine Set-Datenstruktur. Es gibt zwei mögliche Workarounds
indexOf(), entfernen Sie Elemente über filter() usw. Dies ist keine sehr schnelle Lösung, aber sie ist einfach zu implementieren. Eine zu beachtende Schwierigkeit ist, dass indexOf() den Wert NaN nicht finden kann.ECMAScript 6 hat die Datenstruktur Set, die für beliebige Werte funktioniert, schnell ist und NaN korrekt behandelt.
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
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');
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.
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
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]
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}
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.
Vereinigung (a ∪ b): 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]).
Schnittmenge (a ∩ b): 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.
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}
Konstruktor
new Set(elements? : Iterable<any>)iterable nicht angeben, wird ein leeres Set erstellt. Wenn Sie ihn angeben, werden die iterierten Werte als Elemente zum Set hinzugefügt. Zum Beispiel const set = new Set(['red', 'green', 'blue']);
Einzelne Set-Elemente
Set.prototype.add(value) : thisvalue zu diesem Set hinzu. Diese Methode gibt this zurück, sodass sie verkettet werden kann.Set.prototype.has(value) : booleanvalue in diesem Set vorhanden ist.Set.prototype.delete(value) : booleanvalue aus diesem Set.Alle Set-Elemente
get Set.prototype.size : numberSet.prototype.clear() : voidIterieren und Schleifen
Set.prototype.values() : Iterable<any>Set.prototype[Symbol.iterator]() : Iterable<any>Set.prototype.values.Set.prototype.forEach((value, key, collection) => void, thisArg?)value und key sind beide auf das Element gesetzt, sodass diese Methode ähnlich wie Map.prototype.forEach funktioniert. Wenn thisArg angegeben ist, wird this für jeden Aufruf darauf gesetzt. Andernfalls wird this auf undefined gesetzt.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.
Set.prototype.entries() : Iterable<[any,any]>Set.prototype.keys() : Iterable<any>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' }
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.
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.
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.
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!');
}
}
}
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)
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.
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.
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.
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
obj.keymap.get(theKey)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.