Blog

Private Eigenschaften in ES2015: das Gute, das Schlechte und das Hässliche

Albert Brand

Aktualisiert Oktober 22, 2025
5 Minuten

code { display: inline !important; } Dieser Beitrag ist Teil einer Serie von ES2015-Beiträgen. Wir werden jede Woche über neue JavaScript-Funktionen berichten! Eine der neuen Funktionen von ECMAScript 2015 ist die WeakMap. Sie hat mehrere Verwendungszwecke, aber einer der am meisten geförderten ist die Speicherung von Eigenschaften, die nur über eine Objektreferenz abgerufen werden können, wodurch im Wesentlichen private Eigenschaften geschaffen werden. Wir zeigen verschiedene Implementierungsansätze und vergleichen sie in Bezug auf Speicherverbrauch und Leistung mit einer Variante mit 'öffentlichen' Eigenschaften.

Ein klassischer Weg Lassen Sie uns mit einem Beispiel beginnen. Wir möchten eine Klasse Rectangle erstellen, der bei der Instanziierung die Breite und Höhe des Rechtecks übergeben wird. Das Objekt bietet eine area() Funktion, die die Fläche des Rechtecks zurückgibt. Das Beispiel soll sicherstellen, dass auf die Breite und die Höhe nicht direkt zugegriffen werden kann, sondern dass sie beide gespeichert werden müssen. Zunächst zum Vergleich eine klassische Art der Definition von 'privaten' Eigenschaften unter Verwendung der ES2015-Klassensyntax. Wir erstellen einfach Eigenschaften mit einem Unterstrich-Präfix in einer Klasse. Dies verbirgt natürlich nichts, aber ein Benutzer weiß, dass diese Werte intern sind und der Benutzer sollte nicht zulassen, dass Code von ihrem Verhalten abhängt. [javascript] class Rectangle { constructor(Breite, Höhe) { this._width = Breite; this._height = Höhe; } area() { return this._width this._height; } } [/javascript] Wir werden einen kleinen Benchmark durchführen. Lassen Sie uns 100.000 Rectangle Objekte erstellen, die Funktion area() aufrufen und den Speicherverbrauch und die Ausführungsgeschwindigkeit messen. Am Ende dieses Beitrags erfahren Sie, wie der Benchmark durchgeführt wurde. In diesem Fall benötigte Chrome ~49ms und verbrauchte ~8Mb des Heaps. WeakMap-Implementierung für jede private Eigenschaft In der folgenden naiven Implementierung führen wir eine WeakMap ein, die eine WeakMap pro privater Eigenschaft verwendet. Die Idee ist, einen Wert zu speichern, der das Objekt selbst als Schlüssel verwendet. Auf diese Weise kann nur der Accessor des WeakMap auf die privaten Daten zugreifen, und der Accessor sollte natürlich nur die instanziierte Klasse sein. Ein Vorteil von WeakMap ist die Garbage Collection der privaten Daten in der Map, wenn das ursprüngliche Objekt selbst gelöscht wird. [javascript] const _width = new WeakMap(); const _height = new WeakMap(); class Rectangle { constructor (width, height) { _width.set(this, width); _height.set(this, height); } area() { return _width.get(this) _height.get(this); [/javascript] } } Um 100.000 Rectangle Objekte zu erstellen und auf die Funktion area() zuzugreifen, benötigte Chrome ~152ms und belegte ~22Mb Heap auf meinem Computer. Wir können es besser machen. Schnellere WeakMap-Implementierung Ein besserer Ansatz wäre es, alle privaten Daten in einem Objekt für jede Rectangle Instanz in einer einzigen WeakMap zu speichern. Dies kann die Anzahl der Suchvorgänge reduzieren, wenn es richtig eingesetzt wird. [javascript] const map = new WeakMap(); class Rectangle { constructor (Breite, Höhe) { map.set(this, { Breite: Breite, Höhe: Höhe }); } area() { const hidden = map.get(this); return hidden.width hidden.height; } } [/javascript] Dieses Mal benötigte Chrome ~89ms und verbrauchte ~21Mb Heap. Wie erwartet, ist der Code schneller, da set und get weniger aufgerufen werden. Interessanterweise ist der Speicherverbrauch mehr oder weniger gleich geblieben, obwohl wir weniger Objektreferenzen speichern. Vielleicht ein Hinweis auf die interne Implementierung von WeakMap in Chrome? WeakMap-Implementierung mit Hilfsfunktionen Um die Lesbarkeit des obigen Codes zu verbessern, könnten wir eine Hilfslib erstellen, die zwei Funktionen exportieren sollte: initInternal und internal, und zwar auf folgende Weise: [javascript] const map = new WeakMap(); let initInternal = function (object) { let data = {}; map.set(object, data); return data; }; let internal = function (object) { return map.get(object); }; [/javascript] Dann können wir die privaten Vars auf folgende Weise initialisieren und verwenden: [javascript] class Rectangle { constructor(width, height) { const int = initInternal(this); int.width = width; int.height = height; } area() { const int = internal(this); return int.width int.height; [/javascript] } } Für das obige Beispiel benötigte Chrome ~108ms und belegte ~23Mb des Heaps. Es ist etwas langsamer als der direkte set/get Aufruf, aber schneller als die separaten Abfragen. Fazit

  • Das Gute: echte Privatgrundstücke sind jetzt möglich
  • Der Nachteil: Es kostet mehr Speicherplatz und verschlechtert die Leistung
  • Das Hässliche: Wir brauchen Hilfsfunktionen, um die Syntax in Ordnung zu bringen

Die WeakMap hat sowohl mit der Leistung als auch mit der Speichernutzung zu kämpfen (zumindest bei den Tests in Chrome). Jede Suche nach einer Objektreferenz in der Map kostet Zeit, und die Speicherung von Daten in einer separaten WeakMap ist weniger effizient als die Speicherung direkt im Objekt selbst. Als Faustregel gilt, dass Sie so wenig Suchvorgänge wie nötig durchführen sollten. Für Ihr Projekt ist die Speicherung privater Eigenschaften in einer WeakMap ein Kompromiss zwischen geringerem Speicherverbrauch und höherer Leistung. Stellen Sie sicher, dass Sie Ihr Projekt mit verschiedenen Implementierungen testen und nicht zu früh in die Falle der Mikro-Optimierung tappen. Testreferenz Stellen Sie sicher, dass Sie Chrome mit den folgenden Parametern ausführen: --enable-precise-memory-info --js-flags="--expose-gc" - dies ermöglicht detaillierte Informationen zum Heap-Speicher und stellt die Funktion gc zur Verfügung, um die Garbage Collection auszulösen. Dann wurde für jede Implementierung der folgende Code ausgeführt: [javascript] const heapUsed = []; const timeUsed = []; for (lassen Sie i = 1; i <= 50; i++) { constances = []; const areas = []; gc(); const t0 = performance.now(); const m0 = performance.memory.usedJSHeapSize; for (lassen Sie j = 1; j <= 100000; j++) { var rectangle = new Rectangle(i, j); instances.push(Rechteck); areas.push(rechteck.bereich()); } const t1 = performance.now(); const m1 = performance.memory.usedJSHeapSize; heapUsed.push(m1 - m0); timeUsed.push(t1 - t0); } var sum = function (alt, val) { return old + val; }; console.log('heapUsed', heapUsed.reduce(sum, 0) / heapUsed.length); console.log('timeUsed', timeUsed.reduce(sum, 0) / heapUsed.length); [/javascript]

Verfasst von

Albert Brand

Contact

Let’s discuss how we can support your journey.