Heute hatte ich eine kleine Aufgabe, JS-Code umzugestalten, und ich stieß auf ein unerwartetes Merkmal der Sprache, über das ich über 7 Jahre Programmiererfahrung gesammelt habe von vielen gehasst Sprache hat nicht gedacht und ist nicht rübergekommen.
Außerdem konnte ich weder im russischen noch im englischen Internet etwas finden und beschloss daher, diese nicht sehr lange, nicht die interessanteste, aber nützliche Notiz zu veröffentlichen.
Um die traditionellen und bedeutungslosen foo/bar
Konstanten nicht zu verwenden, werde ich direkt auf das Beispiel zeigen, das wir im Projekt hatten, aber immer noch ohne interne Logik und mit falschen Werten. Denken Sie daran, dass sich die Beispiele dennoch als ziemlich synthetisch herausstellten.
Wir treten auf einen Rechen
Wir haben also eine Klasse:
class BaseTooltip { template = 'baseTemplate' constructor(content) { this.render(content) } render(content) { console.log('render:', content, this.template) } } const tooltip = new BaseTooltip('content')
Alles ist logisch
Und dann mussten wir einen anderen Tooltip erstellen, in dem sich das template
ändert
class SpecialTooltip extends BaseTooltip { template = 'otherTemplate' }
Und hier erwartete mich eine Überraschung, denn beim Erstellen eines Objekts eines neuen Typs geschieht Folgendes
const specialTooltip = new SpecialTooltip('otherContent')
Die BaseTooltip.prototype.template
wurde mit dem Wert BaseTooltip.prototype.template
, nicht wie erwartet mit BaseTooltip.prototype.template
.
Wir treten vorsichtig auf den Rechen, Video aufnehmen
Da Chrome DevTools nicht wissen, wie Klassenfelder zugewiesen werden, müssen Sie auf Tricks zurückgreifen, um zu verstehen, was passiert. Mit einem kleinen Helfer protokollieren wir den Moment der Zuweisung zu einer Variablen
function logAndReturn(value) { console.log(`set property=${value}`) return value } class BaseTooltip { template = logAndReturn('baseTemplate') constructor(content) { console.log(`call constructor with property=${this.template}`) this.render(content) } render(content) { console.log(content, this.template) } } const tooltip = new BaseTooltip('content')
Und wenn wir diesen Ansatz auf die geerbte Klasse anwenden, erhalten wir Folgendes Seltsames:
class SpecialTooltip extends BaseTooltip { template = logAndReturn('otherTemplate') } const tooltip = new SpecialTooltip('content')
Ich war mir sicher, dass die Felder des Objekts zuerst initialisiert werden und dann der Rest des Konstruktors aufgerufen wird. Es stellt sich heraus, dass alles schwieriger ist.
Wir treten auf den Rechen und malen den Stiel
Wir erschweren die Situation, indem wir dem Konstruktor einen weiteren Parameter hinzufügen, den wir unserem Objekt zuweisen
class BaseTooltip { template = logAndReturn('baseTemplate') constructor(content, options) { this.options = logAndReturn(options)
Und nur diese Art des Debuggens (naja, keine Warnungen) hat ein wenig geklärt
Woher kam dieses Problem:Zuvor wurde dieser Code in das Marionette-Framework geschrieben und sah (bedingt) so aus
const BaseTooltip = Marionette.Object.extend({ template: 'baseTemplate', initialize(content) { this.render(content) }, render(content) { console.log(content, this.template) }, }) const SpecialTooltip = BaseTooltip.extend({ template: 'otherTemplate' })
Bei Verwendung von Marionette funktionierte alles wie erwartet, render
die render
wurde mit dem in der Klasse angegebenen template
aufgerufen. Beim Kopieren der Modullogik nach ES6 trat jedoch das im Artikel beschriebene Problem auf
Zähle die Unebenheiten
Das Ergebnis:
Wenn Sie ein Objekt einer geerbten Klasse erstellen, ist die Reihenfolge der Vorgänge wie folgt:
- Objektfelder aus einer geerbten Klassendeklaration initialisieren
- Ausführung des Konstruktors der geerbten Klasse (einschließlich Initialisierung der Felder im Konstruktor)
- Erst nach dieser Initialisierung der Felder des Objekts aus der aktuellen Klasse
- Ausführen des Konstruktors der aktuellen Klasse
Wir bringen den Rechen in die Scheune zurück
Insbesondere in meiner Situation kann das Problem entweder durch Mixins oder durch Übergeben der Vorlage an den Konstruktor gelöst werden. Wenn die Anwendungslogik jedoch das Überschreiben einer großen Anzahl von Feldern erfordert, wird dies zu einem ziemlich schmutzigen Weg.
Es wäre schön, in den Kommentaren Ihre Vorschläge zur eleganten Lösung des Problems zu lesen.