Come estendere una classe senza dover usare super in ES6?


98

È possibile estendere una classe in ES6 senza chiamare il supermetodo per richiamare la classe genitore?

EDIT: la domanda potrebbe essere fuorviante. È lo standard che dobbiamo chiamare super()o mi manca qualcosa?

Per esempio:

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();

Quando non chiamo super()la classe derivata ricevo un problema di ambito ->this is not defined

Lo sto eseguendo con iojs --harmony nella v2.3.0


Cosa intendi per problema di ambito? Ricevi un'eccezione (e dove)?
Amit

Ricevo l'aspettativa nella mia classe derivata quando la richiamo senza chiamare super (). Ho modificato la mia domanda per renderla più chiara :)
xhallix

In quale ambiente lo stai eseguendo?
Amit

6
Non hai scelta se estendi un'altra classe, il costruttore deve prima chiamare super ().
Jonathan de M.

@JonathandeM. grazie, quindi è così che dovrebbe essere in futuro, immagino allora?
xhallix

Risposte:


147

Le regole per le classi ES2015 (ES6) si riducono sostanzialmente a:

  1. In un costruttore di classi figlio, thisnon può essere utilizzato finché non superviene chiamato.
  2. I costruttori di classi ES6 DEVONO chiamare superse sono sottoclassi, o devono restituire esplicitamente un oggetto per prendere il posto di quello che non è stato inizializzato.

Ciò si riduce a due importanti sezioni delle specifiche ES2015.

La sezione 8.1.1.3.4 definisce la logica per decidere cosa thisc'è nella funzione. La parte importante per le classi è che è possibile thistrovarsi in uno "uninitialized"stato, e quando in questo stato, il tentativo di utilizzare thisgenererà un'eccezione.

Sezione 9.2.2 , [[Construct]]che definisce il comportamento delle funzioni chiamate tramite newo super. Quando si chiama un costruttore di una classe base, thisviene inizializzato al passaggio 8 di [[Construct]], ma per tutti gli altri casi thisnon è inizializzato. Alla fine della costruzione, GetThisBindingviene chiamato, quindi se supernon è stato ancora chiamato (quindi inizializzazione this), o non è stato restituito un oggetto sostitutivo esplicito, la riga finale della chiamata al costruttore genererà un'eccezione.


1
Potresti suggerire un modo per ereditare da una classe senza chiamare super()?
Bergi

4
Pensi che sia possibile in ES6 ereditare da una classe senza chiamare super()il costruttore?
Bergi

8
Grazie per la modifica - è proprio lì ora; puoi fare return Object.create(new.target.prototype, …)per evitare di chiamare il super costruttore.
Bergi

2
@Maximus See Cos'è "new.target"?
Bergi

1
È necessario aggiungere cosa accade se il costruttore viene omesso: verrà utilizzato un costruttore predefinito in base a MDN .
kleinfreund

11

Ci sono state più risposte e commenti che affermano che super DEVE essere la prima riga all'interno constructor. Questo è semplicemente sbagliato. La risposta di @loganfsmyth contiene i riferimenti richiesti dei requisiti, ma si riduce a:

Il extendscostruttore Inheriting ( ) deve chiamare superprima di utilizzare thise prima di restituire anche se thisnon viene utilizzato

Vedere il frammento di seguito (funziona in Chrome ...) per vedere perché potrebbe avere senso avere istruzioni (senza utilizzare this) prima di chiamare 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());


2
"See fragment below (works in Chrome...)"aprire la console cromata sviluppatore e cliccare su "Codice Run frammento di": Uncaught ReferenceError: this is not defined. Certo, puoi usare i metodi nel costruttore prima, super()ma non puoi usare i metodi della classe prima!
marcel

che non puoi usare thisprima super()(il tuo codice lo dimostra) non ha nulla a che fare con la specifica immediatamente, ma con l'implementazione di javascript. Quindi, devi chiamare "super" prima di "questo".
marcel

@marcel - Penso che abbiamo avuto molta confusione. Stavo solo affermando (fin dall'inizio) che è legale avere STATEMENTS prima dell'uso supere tu stavi affermando che è illegale da usare thisprima di chiamare super. Abbiamo entrambi ragione, semplicemente non ci capivamo :-) (E quell'eccezione era intenzionale, per mostrare ciò che non è legale - ho persino chiamato la proprietà 'WillFail')
Amit

9

La nuova sintassi della classe es6 è solo un'altra notazione per le "vecchie" classi "es5" con prototipi. Pertanto non è possibile creare un'istanza di una classe specifica senza impostarne il prototipo (la classe base).

È come mettere il formaggio sul tuo panino senza farlo. Inoltre non puoi mettere il formaggio prima di fare il panino, quindi ...

... anche l'uso della thisparola chiave prima di chiamare la super classe con super()non è consentito.

// 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";
    }
}

Se non specifichi un costruttore per una classe base, viene utilizzata la seguente definizione:

constructor() {}

Per le classi derivate, viene utilizzato il seguente costruttore predefinito:

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

EDIT: Trovato su developer.mozilla.org:

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

fonte


quindi se ti capisco correttamente, non sarò in grado di utilizzare alcun metodo dalla classe Character sulla mia classe Hero, quando non invoco super ()? Ma questo sembra non essere completamente corretto, perché posso chiamare i metodi dalla classe base. quindi immagino di aver bisogno di super quando chiamo il costruttore
xhallix

1. Nell'OP, thisnon viene utilizzato affatto. 2. JS non è un sandwich e in ES5 potresti sempre usarlo this, anche prima di chiamare qualsiasi altra funzione che ti piace (che potrebbe o non potrebbe definire la proprietà del supplemento)
Amit

@amit 1. E ora non posso usare thisanche io ?! 2. La mia classe JS rappresenta un sandwich, e in ES6 non puoi sempre usarlo this. Sto solo cercando di spiegare le classi es6 (con una metafora) e nessuno ha bisogno di commenti così distruttivi / inutili.
marcel

@marcel Le mie scuse per il cinismo, ma: 1. si trattava di introdurre un nuovo problema che non esisteva nell'OP. 2 è per indicare alla tua attenzione che la tua affermazione è sbagliata (il che è ancora il caso)
Amit

@marcel - Vedi la mia risposta
Amit

4

Mi sono appena registrato per pubblicare questa soluzione poiché le risposte qui non mi soddisfano minimamente poiché in realtà c'è un modo semplice per aggirare questo. Regola il tuo modello di creazione della classe per sovrascrivere la logica in un sotto-metodo usando solo il super costruttore e inoltrare gli argomenti del costruttore ad esso.

Come in voi non create un costruttore nelle vostre sottoclassi di per sé, ma fate riferimento solo a un metodo che viene sovrascritto nella rispettiva sottoclasse.

Ciò significa che ti liberi dalla funzionalità del costruttore imposta su di te e ti astieni a un metodo regolare - che può essere ignorato e non impone super () su di te lasciandoti la scelta se, dove e come vuoi chiamare super (completamente opzionale) ad esempio:

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");

Saluti!


2
ho bisogno di una "spiegazione come se avessi cinque anni" su questo .. sento che questa è una risposta molto profonda ma complicata e quindi ignorata
swyx

@swyx: la magia è all'interno del costruttore, dove "questo" si riferisce a un diverso tipo di oggetto a seconda del tipo di oggetto che stai creando. Ad esempio, se stai costruendo un nuovo DomainObservable, this.ObjectConstructor si riferisce a un metodo diverso, ad esempio DomainObserveable.ObjectConstructor; mentre se stai costruendo un nuovo ArrayObservable, this.ObjectConstructor si riferisce a ArrayObservable.ObjectConstructor.
cobberboy

Vedi la mia risposta, ho pubblicato un esempio molto più semplice
Michael Lewis

Sono completamente d'accordo @swyx; questa risposta sta facendo troppo ... l'ho solo sfogliata e sono già stanco. Mi sento come se "spieghi come se avessi cinque anni E devo davvero fare pipì ..."
spb

4

Puoi omettere super () nella tua sottoclasse, se ometti del tutto il costruttore nella tua sottoclasse. Un costruttore predefinito "nascosto" verrà incluso automaticamente nella tua sottoclasse. Tuttavia, se includi il costruttore nella tua sottoclasse, super () deve essere chiamato in quel costruttore.

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

Leggi questo per maggiori informazioni.


2

La risposta di justyourimage è il modo più semplice, ma il suo esempio è un po 'gonfio. Ecco la versione generica:

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();
    }
}

Non estendere il reale constructor(), usa solo il falso _constructor()per la logica di istanziazione.

Nota, questa soluzione rende fastidioso il debug perché devi passare a un metodo aggiuntivo per ogni istanza.


Sì, questo è di gran lunga il metodo più semplice e la risposta più chiara - la domanda che chiedo è ... "Perché, Dio, PERCHÉ!?" ... C'è una ragione molto valida per cui super () non è automatico. .. potresti voler passare parametri specifici alla tua classe base, così puoi fare un po 'di elaborazione / logica / pensiero prima di istanziare la classe base.
LFLFM

1

Provare:

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


in questo esempio usi anche super () e se lo lasci, viene generata un'eccezione. Quindi penso che non sia possibile omettere questa chiamata super () in questo caso
xhallix

Pensavo che il tuo obiettivo non fosse eseguire il costruttore genitore, questo è ciò che fa questo codice. Non puoi sbarazzarti di super quando estendi.
Jonathan de M.

scusa per essere fuorviante :) No, volevo solo sapere se è davvero necessario usare super () poiché mi chiedo la sintassi perché in altri linguaggi non dobbiamo invocare il super metodo quando si chiama il costruttore per la classe derivata
xhallix

1
Secondo developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.quindi direi di aspettare il rilascio di ES6
Jonathan de M.

3
"quindi direi di aspettare per il rilascio ES6" --- è già stato rilasciato già, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms

1

Consiglierei di utilizzare OODK-JS se intendi sviluppare i seguenti concetti OOP.

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);
});

0

Soluzione semplice: penso che sia chiaro che non c'è bisogno di spiegazioni.

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
    }
}

Non sono sicuro del motivo per cui tutto il codice boilerplate sopra e non sono esattamente sicuro che ci siano effetti collaterali a questo approccio. Per me funziona senza problemi.
MyUserInStackOverflow

1
Un problema: se vuoi estendere nuovamente la tua sottoclasse, dovrai creare la funzione skipConstructor nel costruttore di ogni sottoclasse
Michael Lewis

0

@Bergi ha menzionato new.target.prototype, ma stavo cercando un esempio concreto che dimostri che puoi accedere this(o meglio, il riferimento all'oggetto con cui sta creando il codice client new, vedi sotto) senza dover chiamare super().

Parlare costa poco, mostrami il codice ... Quindi ecco un esempio:

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)

Che produrrà:

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

Quindi puoi vedere che stiamo effettivamente creando un oggetto di tipo B(la classe figlia) che è anche un oggetto di tipo A(la sua classe genitore) e all'interno childMethod()di figlio Babbiamo thispuntato all'oggetto objche abbiamo creato in B constructorcon Object.create(new.target.prototype).

E tutto questo senza preoccuparsene superaffatto.

Ciò sfrutta il fatto che in JS a constructorpuò restituire un oggetto completamente diverso quando il codice client costruisce una nuova istanza con new.

Spero che questo aiuti qualcuno.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.