Wie kann man eine Klasse erweitern, ohne Super in ES6 verwenden zu müssen?

98

Ist es möglich, eine Klasse in ES6 zu erweitern, ohne die superMethode zum Aufrufen der übergeordneten Klasse aufzurufen?

EDIT: Die Frage könnte irreführend sein. Ist es der Standard, den wir anrufen müssen, super()oder fehlt mir etwas?

Beispielsweise:

class Character {
   constructor(){
      console.log('invoke character');
   }
}

class Hero extends Character{
  constructor(){
      super(); // exception thrown here when not called
      console.log('invoke hero');
  }
}

var hero = new Hero();

Wenn ich super()die abgeleitete Klasse nicht aufrufe, tritt ein Bereichsproblem auf ->this is not defined

Ich führe dies mit iojs --harmony in v2.3.0 aus

xhallix
quelle
Was meinst du mit Umfangsproblem? Erhalten Sie eine Ausnahme (und wo)?
Amit
Ich erhalte die Erwartung in meiner abgeleiteten Klasse, wenn ich sie aufrufe, ohne super () aufzurufen. Ich habe meine Frage bearbeitet, um es klarer zu machen :)
xhallix
In welcher Umgebung führen Sie dies aus?
Amit
6
Sie haben keine Wahl, wenn Sie eine andere Klasse erweitern, die der Konstruktor zuerst super () aufrufen muss.
Jonathan de M.
@ JonathandeM. Danke, also ist es so, wie es in Zukunft sein soll, denke ich dann?
Xhallix

Antworten:

147

Die Regeln für ES2015 (ES6) -Klassen lauten im Wesentlichen auf:

  1. thisKann in einem untergeordneten Klassenkonstruktor erst verwendet werden, wenn er superaufgerufen wird.
  2. ES6-Klassenkonstruktoren MÜSSEN aufrufen, superwenn es sich um Unterklassen handelt, oder sie müssen explizit ein Objekt zurückgeben, um den Platz des nicht initialisierten Objekts einzunehmen.

Dies ist auf zwei wichtige Abschnitte der ES2015-Spezifikation zurückzuführen.

Abschnitt 8.1.1.3.4 definiert die Logik, um zu entscheiden, was thisin der Funktion enthalten ist. Der wichtige Teil für Klassen ist, dass es möglich ist, thissich in einem "uninitialized"Zustand zu befinden. Wenn Sie sich in diesem Zustand befinden, wird beim Versuch, eine Verwendung zu verwenden this, eine Ausnahme ausgelöst.

Abschnitt 9.2.2 , [[Construct]]der das Verhalten von Funktionen definiert, die über newoder aufgerufen werden super. Wird beim Aufrufen eines Basisklassenkonstruktors thisin Schritt 8 von initialisiert, in [[Construct]]allen anderen Fällen thisjedoch nicht initialisiert. GetThisBindingWird am Ende der Konstruktion aufgerufen. Wenn also supernoch nicht aufgerufen wurde (also initialisiert this) oder kein explizites Ersatzobjekt zurückgegeben wurde, löst die letzte Zeile des Konstruktoraufrufs eine Ausnahme aus.

loganfsmyth
quelle
1
Könnten Sie einen Weg vorschlagen, von einer Klasse zu erben, ohne anzurufen super()?
Bergi
4
Denken Sie, dass es in ES6 möglich ist, von einer Klasse zu erben, ohne super()den Konstruktor aufzurufen ?
Bergi
8
Danke für die Bearbeitung - es ist jetzt genau dort; Sie können dies return Object.create(new.target.prototype, …)vermeiden, indem Sie den Superkonstruktor aufrufen.
Bergi
2
@Maximus Siehe Was ist "new.target"?
Bergi
1
Sie sollten hinzufügen, was passiert, wenn der Konstruktor weggelassen wird: Ein Standardkonstruktor wird gemäß MDN verwendet .
Kleinfreund
11

Es gab mehrere Antworten und Kommentare, die super besagten, dass die erste Zeile darin sein mussconstructor . Das ist einfach falsch. @loganfsmyth Antwort hat die erforderlichen Referenzen der Anforderungen, aber es läuft auf:

Der extendsKonstruktor Inheriting ( ) musssuper vor der Verwendung thisund vor der Rückgabe aufrufen , auch wennthis nicht verwendet wird

Im folgenden Fragment (funktioniert in Chrome ...) erfahren Sie, warum es möglicherweise sinnvoll ist, thisvor dem Aufruf Anweisungen (ohne Verwendung ) zu haben super.

'use strict';
var id = 1;
function idgen() {
  return 'ID:' + id++;
}

class Base {
  constructor(id) {
    this.id = id;
  }

  toString() { return JSON.stringify(this); }
}

class Derived1 extends Base {
  constructor() {
    var anID = idgen() + ':Derived1';
    super(anID);
    this.derivedProp = this.baseProp * 2;
  }
}

alert(new Derived1());

Amit
quelle
2
"See fragment below (works in Chrome...)"Öffnen Sie die Chrome-Entwicklerkonsole und klicken Sie auf "Code-Snippet ausführen" : Uncaught ReferenceError: this is not defined. Natürlich können Sie vorher Methoden im Konstruktor verwenden, super()aber Sie können vorher keine Methoden aus der Klasse verwenden!
Marcel
Das, was Sie thisvorher nicht verwenden können super()(Ihr Code beweist es), hat nichts mit der sofortigen Spezifikation zu tun, sondern mit der Implementierung von Javascript. Also, müssen Sie ‚super‘ nennen , bevor ‚this‘.
Marcel
@marcel - Ich denke wir hatten viel Verwirrung. Ich habe nur (die ganze Zeit) erklärt, dass es legal ist, STATEMENTS vor der Verwendung zu haben super, und Sie haben angegeben, dass die Verwendung thisvor dem Anruf illegal ist super. Wir haben beide Recht, haben uns einfach nicht verstanden :-) (Und diese Ausnahme war beabsichtigt, um zu zeigen, was nicht legal ist - ich habe sogar die Eigenschaft 'WillFail' genannt)
Amit
9

Die neue Syntax der es6-Klasse ist nur eine andere Notation für "alte" es5-Klassen mit Prototypen. Daher können Sie eine bestimmte Klasse nicht instanziieren, ohne ihren Prototyp (die Basisklasse) festzulegen.

Das ist so, als würde man Käse auf das Sandwich legen, ohne es zuzubereiten. Sie können auch keinen Käse vor das Sandwich legen , also ...

... die Verwendung eines thisSchlüsselworts vor dem Aufruf der Superklasse mit super()ist ebenfalls nicht zulässig.

// valid: Add cheese after making the sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        super();
        this.supplement = "Cheese";
    }
}

// invalid: Add cheese before making sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        this.supplement = "Cheese";
        super();
    }
}

// invalid: Add cheese without making sandwich
class CheeseSandwich extend Sandwich {
    constructor() {
        this.supplement = "Cheese";
    }
}

Wenn Sie keinen Konstruktor für eine Basisklasse angeben, wird die folgende Definition verwendet:

constructor() {}

Für abgeleitete Klassen wird der folgende Standardkonstruktor verwendet:

constructor(...args) {
    super(...args);
}

EDIT: Gefunden am developer.mozilla.org:

When used in a constructor, the super keyword appears alone and must be used before the this keyword can be used.

Quelle

marcel
quelle
Wenn ich Sie also richtig verstehe, kann ich keine Methode aus der Klasse Character für meine Klasse Hero verwenden, wenn ich nicht super () aufrufe. Dies scheint jedoch nicht vollständig korrekt zu sein, da ich die Methoden aus der Basisklasse aufrufen kann.
Also
1. Wird im OP überhaupt thisnicht verwendet. 2. JS ist kein Sandwich, und in ES5 können Sie es immer verwenden this, noch bevor Sie eine andere Funktion aufrufen, die Ihnen gefällt (die möglicherweise die Supplement-Eigenschaft definiert oder nicht)
Amit
@amit 1. Und jetzt kann ich nicht auch verwenden this?! 2. Meine JS-Klasse stellt ein Sandwich dar, und in ES6 können Sie es nicht immer verwenden this. Ich versuche nur, es6-Klassen (mit einer Metapher) zu erklären, und niemand braucht solche destruktiven / unnötigen Kommentare.
Marcel
@marcel Ich entschuldige mich für den Zynismus, aber: 1. ging es darum, ein neues Problem einzuführen, das es im OP nicht gab. 2 ist Ihre Aufmerksamkeit zu lenken, dass Ihre Behauptung falsch ist (was immer noch der Fall ist)
Amit
@ Marcel - Siehe meine Antwort
Amit
4

Ich habe mich gerade registriert, um diese Lösung zu veröffentlichen, da die Antworten hier mich nicht im geringsten befriedigen, da es tatsächlich einen einfachen Weg gibt, dies zu umgehen. Passen Sie Ihr Klassenerstellungsmuster an, um Ihre Logik in einer Untermethode zu überschreiben, während Sie nur den Superkonstruktor verwenden, und leiten Sie die Konstruktorargumente an diese weiter.

Wie in erstellen Sie keinen Konstruktor in Ihren Unterklassen an sich, sondern verweisen nur auf eine Methode, die in der jeweiligen Unterklasse überschrieben wird.

Das bedeutet, dass Sie sich von der Konstruktorfunktionalität befreien, die Ihnen auferlegt wurde, und auf eine reguläre Methode verzichten - die überschrieben werden kann und super () nicht erzwingt, wenn Sie die Wahl haben, ob, wo und wie Sie super (vollständig) aufrufen möchten optional) zB:

super.ObjectConstructor(...)

class Observable {
  constructor() {
    return this.ObjectConstructor(arguments);
  }

  ObjectConstructor(defaultValue, options) {
    this.obj = { type: "Observable" };
    console.log("Observable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

class ArrayObservable extends Observable {
  ObjectConstructor(defaultValue, options, someMoreOptions) {
    this.obj = { type: "ArrayObservable" };
    console.log("ArrayObservable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

class DomainObservable extends ArrayObservable {
  ObjectConstructor(defaultValue, domainName, options, dependent1, dependent2) {
    this.obj = super.ObjectConstructor(defaultValue, options);
    console.log("DomainObservable ObjectConstructor called with arguments: ", arguments);
    console.log("obj is:", this.obj);
    return this.obj;
  }
}

var myBasicObservable = new Observable("Basic Value", "Basic Options");
var myArrayObservable = new ArrayObservable("Array Value", "Array Options", "Some More Array Options");
var myDomainObservable = new DomainObservable("Domain Value", "Domain Name", "Domain Options", "Dependency A", "Depenency B");

Prost!

nur dein Bild
quelle
2
Ich brauche ein "Erklären, wie ich fünf bin" in diesem
Fall. Ich denke,
@swyx: Die Magie befindet sich im Konstruktor, wobei sich 'this' auf einen anderen Objekttyp bezieht, je nachdem, welchen Objekttyp Sie erstellen. Wenn Sie beispielsweise ein neues DomainObservable erstellen, verweist this.ObjectConstructor auf eine andere Methode, z. B. DomainObserveable.ObjectConstructor. Wenn Sie ein neues ArrayObservable erstellen, bezieht sich this.ObjectConstructor auf ArrayObservable.ObjectConstructor.
Cobberboy
Siehe meine Antwort, ich habe ein viel einfacheres Beispiel gepostet
Michael Lewis
Ich stimme @swyx voll und ganz zu; Diese Antwort macht viel zu viel ... Ich habe sie nur überflogen und bin schon müde. Ich fühle mich wie "erklären, wie ich fünf bin und wirklich pinkeln muss ..."
spb
4

Sie können super () in Ihrer Unterklasse weglassen, wenn Sie den Konstruktor in Ihrer Unterklasse ganz weglassen. Ein 'versteckter' Standardkonstruktor wird automatisch in Ihre Unterklasse aufgenommen. Wenn Sie jedoch den Konstruktor in Ihre Unterklasse aufnehmen, muss super () in diesem Konstruktor aufgerufen werden.

class A{
   constructor(){
      this.name = 'hello';   
   }
}
class B extends A{
   constructor(){
      // console.log(this.name); // ReferenceError
      super();
      console.log(this.name);
   }
}
class C extends B{}  // see? no super(). no constructor()

var x = new B; // hello
var y = new C; // hello

Lesen Sie dies für weitere Informationen.

Chong Lip Phang
quelle
2

Die Antwort von justyourimage ist der einfachste Weg, aber sein Beispiel ist ein wenig aufgebläht. Hier ist die generische Version:

class Base {
    constructor(){
        return this._constructor(...arguments);
    }

    _constructor(){
        // just use this as the constructor, no super() restrictions
    }
}

class Ext extends Base {
    _constructor(){ // _constructor is automatically called, like the real constructor
        this.is = "easy"; // no need to call super();
    }
}

Erweitern Sie nicht die reale constructor(), verwenden Sie nur die Fälschung_constructor() für die Instanziierungslogik.

Beachten Sie, dass diese Lösung das Debuggen ärgerlich macht, da Sie für jede Instanziierung eine zusätzliche Methode anwenden müssen.

Michael Lewis
quelle
Ja, dies ist bei weitem die einfachste Methode und die klarste Antwort - die Frage, die ich stelle, ist ... "Warum, Gott, WARUM !?" ... Es gibt einen sehr triftigen Grund, warum super () nicht automatisch ist. .. Sie möchten möglicherweise bestimmte Parameter an Ihre Basisklasse übergeben, damit Sie einige Verarbeitungen / Logik / Überlegungen durchführen können, bevor Sie die Basisklasse instanziieren.
LFLFM
1

Versuchen:

class Character {
   constructor(){
     if(Object.getPrototypeOf(this) === Character.prototype){
       console.log('invoke character');
     }
   }
}


class Hero extends Character{
  constructor(){
      super(); // throws exception when not called
      console.log('invoke hero');
  }
}
var hero = new Hero();

console.log('now let\'s invoke Character');
var char = new Character();

Demo

Jonathan de M.
quelle
In diesem Beispiel verwenden Sie auch super () und wenn Sie es verlassen, wird eine Ausnahme ausgelöst. Daher denke ich, dass es in diesem Fall nicht möglich ist, diesen super ()
-Aufruf
Ich dachte, Ihr Ziel wäre es nicht, den übergeordneten Konstruktor auszuführen, das ist, was dieser Code tut. Sie können nicht super loswerden, wenn Sie verlängern.
Jonathan de M.
Entschuldigung für die Irreführung :) Nein, ich wollte nur wissen, ob es wirklich notwendig ist, super () zu verwenden, da ich mich über die Syntax wundere, weil wir in anderen Sprachen die super-Methode nicht aufrufen müssen, wenn wir den Konstruktor für die abgeleitete Klasse aufrufen
xhallix
1
Nach developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/... , A constructor *can* use the super keyword to call the constructor of a parent class.so würde ich sagen , Warten auf ES6 Release
Jonathan de M.
3
" Also
zerkms
1

Ich würde empfehlen, OODK-JS zu verwenden, wenn Sie folgende OOP-Konzepte entwickeln möchten .

OODK(function($, _){

var Character  = $.class(function ($, µ, _){

   $.public(function __initialize(){
      $.log('invoke character');
   });
});

var Hero = $.extends(Character).class(function ($, µ, _){

  $.public(function __initialize(){
      $.super.__initialize();
      $.log('invoke hero');
  });
});

var hero = $.new(Hero);
});
OBDM
quelle
0

Einfache Lösung: Ich denke, es ist klar, dass es keiner Erklärung bedarf.

class ParentClass() {
    constructor(skipConstructor = false) { // default value is false
        if(skipConstructor) return;
        // code here only gets executed when 'super()' is called with false
    }
}
class SubClass extends ParentClass {
    constructor() {
        super(true) // true for skipping ParentClass's constructor.
        // code
    }
}
MyUserInStackOverflow
quelle
Ich bin mir nicht sicher, warum all der oben genannte Boilerplate-Code, und ich bin nicht genau sicher, ob dieser Ansatz Nebenwirkungen hat. Es funktioniert bei mir ohne Probleme.
MyUserInStackOverflow
1
Ein Problem: Wenn Sie Ihre SubClass erneut erweitern möchten, müssen Sie die Funktion skipConstructor in jeden SubClass-Konstruktor einbauen
Michael Lewis,
0

@Bergi erwähnt new.target.prototype, aber ich suchte nach einem konkreten Beispiel, das beweist, dass Sie auf this(oder besser auf den Verweis auf das Objekt, mit dem der Client-Code erstellt new, siehe unten) zugreifen können , ohne überhaupt aufrufen super()zu müssen.

Sprechen ist billig, zeig mir den Code ... Also hier ein Beispiel:

class A { // Parent
    constructor() {
        this.a = 123;
    }

    parentMethod() {
        console.log("parentMethod()");
    }
}

class B extends A { // Child
    constructor() {
        var obj = Object.create(new.target.prototype)
        // You can interact with obj, which is effectively your `this` here, before returning
        // it to the caller.
        return obj;
    }

    childMethod(obj) {
        console.log('childMethod()');
        console.log('this === obj ?', this === obj)
        console.log('obj instanceof A ?', obj instanceof A);
        console.log('obj instanceof B ?',  obj instanceof B);
    }
}

b = new B()
b.parentMethod()
b.childMethod(b)

Welches wird ausgegeben:

parentMethod()
childMethod()
this === obj ? true
obj instanceof A ? true
obj instanceof B ? true

So können Sie sehen , dass wir effektiv ein Objekt vom Typ erstellen B(das Kind Klasse) , die auch ein Objekt vom Typ ist A(seine Elternklasse) und innerhalb der childMethod()von Kind Bhaben wir thisauf das Objekt zeigen , objdie wir in B erstellt constructormitObject.create(new.target.prototype) .

Und das alles, ohne sich darum superzu kümmern .

Dies nutzt die Tatsache, dass in JS a constructorein völlig anderes Objekt zurückgegeben werden kann, wenn der Clientcode eine neue Instanz mit erstelltnew .

Hoffe das hilft jemandem.

Tonix
quelle