Comment étendre une classe sans avoir à utiliser super dans ES6?

98

Est-il possible d'étendre une classe dans ES6 sans appeler la superméthode pour appeler la classe parent?

EDIT: La question pourrait être trompeuse. Est-ce la norme que nous devons appeler super()ou est-ce que je manque quelque chose?

Par exemple:

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

Quand je n'appelle pas super()la classe dérivée, j'obtiens un problème de portée ->this is not defined

J'exécute ceci avec iojs --harmony dans la v2.3.0

xhallix
la source
Que voulez-vous dire par problème de portée? Obtenez-vous une exception (et où)?
Amit le
J'obtiens l'attente dans ma classe dérivée lorsque je l'invoque sans appeler super (). J'ai édité ma question pour la rendre plus claire :)
xhallix
Dans quel environnement exécutez-vous cela?
Amit le
6
Vous n'avez pas le choix si vous étendez une autre classe, le constructeur doit d'abord appeler super ().
Jonathan de M.
@JonathandeM. merci, donc c'est comme ça que c'est censé être dans le futur je suppose alors?
xhallix

Réponses:

147

Les règles pour les classes ES2015 (ES6) se résument essentiellement à:

  1. Dans un constructeur de classe enfant, thisne peut pas être utilisé tant qu'il supern'est pas appelé.
  2. Les constructeurs de classe ES6 DOIVENT appeler supers'ils sont des sous-classes, ou ils doivent renvoyer explicitement un objet pour remplacer celui qui n'a pas été initialisé.

Cela se résume à deux sections importantes de la spécification ES2015.

La section 8.1.1.3.4 définit la logique pour décider de ce qu'il thisy a dans la fonction. La partie importante pour les classes est qu'il est possible d' thisêtre dans un "uninitialized"état, et quand dans cet état, une tentative d'utilisation thislèvera une exception.

La section 9.2.2 , [[Construct]]qui définit le comportement des fonctions appelées via newou super. Lors de l'appel d'un constructeur de classe de base, thisest initialisé à l'étape 8 de [[Construct]], mais dans tous les autres cas, il thisn'est pas initialisé. A la fin de la construction, GetThisBindingest appelé, donc si supern'a pas encore été appelé (donc initialisation this), ou si un objet de remplacement explicite n'a pas été retourné, la dernière ligne de l'appel du constructeur lèvera une exception.

loganfsmyth
la source
1
Pouvez-vous suggérer un moyen d'hériter d'une classe sans appeler super()?
Bergi
4
Pensez-vous qu'il est possible dans ES6 d'hériter d'une classe sans appeler super()le constructeur?
Bergi
8
Merci pour la modification - c'est là maintenant; vous pouvez faire return Object.create(new.target.prototype, …)pour éviter d'appeler le super constructeur.
Bergi
2
@Maximus See Qu'est-ce que «new.target»?
Bergi
1
Vous devez ajouter ce qui se passe si le constructeur est omis: Un constructeur par défaut sera utilisé selon MDN .
kleinfreund
11

Il y a eu plusieurs réponses et commentaires indiquant que super DOIT être la première ligne à l'intérieur constructor. C'est tout simplement faux. @loganfsmyth answer a les références requises des exigences, mais cela se résume à:

Le extendsconstructeur Inheriting ( ) doit appeler superavant d'utiliser thiset avant de retourner même s'il thisn'est pas utilisé

Voir le fragment ci-dessous (fonctionne dans Chrome ...) pour voir pourquoi il peut être judicieux d'avoir des instructions (sans utiliser this) avant d'appeler 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
la source
2
"See fragment below (works in Chrome...)"ouvrir la console développeur de chrome et cliquez sur « Exécuter extrait de code »: Uncaught ReferenceError: this is not defined. Bien sûr, vous pouvez utiliser des méthodes dans le constructeur avant super()mais vous ne pouvez pas utiliser des méthodes de la classe avant!
marcel
que vous ne pouvez pas utiliser thisauparavant super()(votre code le prouve) n'a rien à voir avec la spécification immédiatement, mais avec l'implémentation de javascript. Donc, vous devez appeler «super» avant «ceci».
marcel
@marcel - Je pense que nous avons eu beaucoup de confusion. Je disais seulement (tout au long) qu'il est légal d'avoir des déclarations avant de l'utiliser super, et vous disiez qu'il est illégal d'utiliser thisavant d'appeler super. Nous avons tous les deux raison, nous ne nous comprenons tout simplement pas :-) (Et cette exception était intentionnelle, pour montrer ce qui n'est pas légal - j'ai même nommé la propriété `` WillFail '')
Amit le
9

La nouvelle syntaxe de classe es6 n'est qu'une autre notation pour les "anciennes" "classes" es5 avec des prototypes. Par conséquent, vous ne pouvez pas instancier une classe spécifique sans définir son prototype (la classe de base).

C'est comme mettre du fromage sur votre sandwich sans le faire. De plus, vous ne pouvez pas mettre de fromage avant de faire le sandwich, alors ...

... l'utilisation d'un thismot-clé avant d'appeler la super classe avec super()n'est pas non plus autorisée.

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

Si vous ne spécifiez pas de constructeur pour une classe de base, la définition suivante est utilisée:

constructor() {}

Pour les classes dérivées, le constructeur par défaut suivant est utilisé:

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

EDIT: trouvé ceci sur developer.mozilla.org:

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

La source

Marcel
la source
donc si je vous comprends bien, je ne pourrai utiliser aucune méthode de la classe Character sur ma classe Hero, si je n'appelle pas super ()? Mais cela ne semble pas être tout à fait correct, car je peux appeler les méthodes à partir de la classe de base. donc je suppose que j'ai juste besoin de super lors de l'appel du constructeur
xhallix
1. Dans l'OP, thisn'est pas du tout utilisé. 2. JS n'est pas un sandwich, et dans ES5, vous pouvez toujours utiliser this, même avant d'appeler toute autre fonction que vous aimez (qui pourrait ou non définir la propriété du supplément)
Amit
@amit 1. Et maintenant, je ne peux pas utiliser thisaussi?! 2. Ma classe JS représente un sandwich et dans ES6 vous ne pouvez pas toujours l'utiliser this. J'essaie juste d'expliquer les classes es6 (avec une métaphore), et personne n'a besoin de tels commentaires destructeurs / inutiles.
marcel
@marcel Mes excuses pour le cynisme, mais: 1. concernait l'introduction d'un nouveau problème qui n'existait pas dans l'OP. 2 est d'attirer votre attention sur le fait que votre réclamation est fausse (ce qui est toujours le cas)
Amit
@marcel - Voir ma réponse
Amit
4

Je viens de m'inscrire pour publier cette solution car les réponses ici ne me satisfont pas le moins car il existe en fait un moyen simple de contourner cela. Ajustez votre modèle de création de classe pour écraser votre logique dans une sous-méthode tout en n'utilisant que le super constructeur et transmettez-lui les arguments du constructeur.

Comme dans, vous ne créez pas de constructeur dans vos sous-classes en soi, mais faites uniquement référence à une méthode qui est remplacée dans la sous-classe respective.

Cela signifie que vous vous libérez de la fonctionnalité de constructeur qui vous est imposée et que vous vous abstenez d'utiliser une méthode régulière - qui peut être remplacée et n'applique pas super () lorsque vous vous laissez le choix si, où et comment vous voulez appeler super (entièrement facultatif) par exemple:

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

à votre santé!

justyourimage
la source
2
j'ai besoin d'un "expliquer comme si j'avais cinq ans" sur celui-ci .. j'ai l'impression que c'est une réponse très profonde mais compliquée et donc ignorée
swyx
@swyx: la magie est à l'intérieur du constructeur, où «ceci» fait référence à un type d'objet différent selon le type d'objet que vous créez. Par exemple, si vous construisez un nouveau DomainObservable, this.ObjectConstructor fait référence à une méthode différente, c'est-à-dire DomainObserveable.ObjectConstructor; tandis que si vous construisez un nouvel ArrayObservable, this.ObjectConstructor fait référence à ArrayObservable.ObjectConstructor.
cobberboy
Voir ma réponse, j'ai posté un exemple beaucoup plus simple
Michael Lewis
Je suis entièrement d'accord @swyx; cette réponse en fait beaucoup trop ... je l'ai seulement écrémée et je suis déjà fatiguée. Je me sens comme "expliquer comme si j'avais cinq ans ET vraiment devoir faire pipi ..."
spb
4

Vous pouvez omettre super () dans votre sous-classe, si vous omettez complètement le constructeur dans votre sous-classe. Un constructeur par défaut «caché» sera automatiquement inclus dans votre sous-classe. Cependant, si vous incluez le constructeur dans votre sous-classe, super () doit être appelé dans ce constructeur.

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

Lisez ceci pour plus d'informations.

Chong Lip Phang
la source
2

La réponse par justyourimage est le moyen le plus simple, mais son exemple est un peu gonflé. Voici la version générique:

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

Ne prolonge pas le réel constructor(), utilise juste le faux_constructor() pour la logique d'instanciation.

Notez que cette solution rend le débogage ennuyeux car vous devez entrer dans une méthode supplémentaire pour chaque instanciation.

Michael Lewis
la source
Oui, c'est de loin la méthode la plus simple et la réponse la plus claire - la question que je pose est ... "Pourquoi, Dieu, POURQUOI!?" ... Il y a une raison très valable pour laquelle super () n'est pas automatique. .. vous voudrez peut-être passer des paramètres spécifiques à votre classe de base, afin que vous puissiez faire un peu de traitement / logique / réflexion avant d'instancier la classe de base.
LFLFM
1

Essayer:

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.
la source
dans cet exemple, vous utilisez également super () et si vous le laissez, vous avez une exception levée. Donc je pense qu'il n'est pas possible d'omettre cet appel super () dans ce cas
xhallix
Je pensais que votre objectif n'était pas d'exécuter le constructeur parent, c'est ce que fait ce code. Vous ne pouvez pas vous débarrasser de super lors de l'extension.
Jonathan de M.
désolé d'être trompeur :) Non je voulais juste savoir s'il est vraiment nécessaire d'utiliser super () car je m'interroge sur la syntaxe car dans d'autres langages, nous n'avons pas à invoquer la super méthode lors de l'appel du constructeur de la classe dérivée
xhallix
1
Selon developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.je dirais donc d'attendre la sortie de l'ES6
Jonathan de M.
3
"donc je dirais d'attendre la sortie de l'ES6" --- il a déjà été publié, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms
1

Je recommanderais d'utiliser OODK-JS si vous avez l'intention de développer les concepts de POO suivants.

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
la source
1

Solution simple: je pense que c'est clair pas besoin d'explication.

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
la source
Je ne sais pas pourquoi tout le code passe-partout ci-dessus, et je ne sais pas exactement s'il y a des effets secondaires à cette approche. Cela fonctionne pour moi sans problème.
MyUserInStackOverflow
1
Un problème: si vous souhaitez étendre à nouveau votre sous-classe, vous devrez intégrer la fonctionnalité skipConstructor dans chaque constructeur de sous
Michael Lewis
0

@Bergi l'a mentionné new.target.prototype, mais je cherchais un exemple concret prouvant que vous pouvez accéder this(ou mieux, la référence à l'objet avec lequel le code client crée new, voir ci-dessous) sans avoir à appeler super()du tout.

Parler c'est pas cher, montrez-moi le code ... Voici donc un exemple:

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)

Qui produira:

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

Ainsi, vous pouvez voir que nous créons effectivement un objet de type B(la classe enfant) qui est également un objet de type A(sa classe parent) et dans le childMethod()of child Bnous avons thispointé vers l'objet objque nous avons créé dans B's constructoravecObject.create(new.target.prototype) .

Et tout cela sans se soucier superdu tout.

Cela tire parti du fait que dans JS, a constructorpeut renvoyer un objet complètement différent lorsque le code client construit une nouvelle instance avecnew .

J'espère que cela aide quelqu'un.

tonix
la source