Jak rozszerzyć klasę bez konieczności używania super w ES6?


98

Czy można rozszerzyć klasę w ES6 bez wywoływania supermetody wywołującej klasę nadrzędną?

EDYCJA: Pytanie może być mylące. Czy to standard, do którego musimy zadzwonić, super()czy czegoś mi brakuje?

Na przykład:

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

Kiedy nie wywołuję super()klasy pochodnej, pojawia się problem z zakresem ->this is not defined

Używam tego z iojs --harmony w wersji 2.3.0


Co masz na myśli, mówiąc problem z zakresem? Czy otrzymujesz wyjątek (i gdzie)?
Amit

Otrzymuję oczekiwanie w mojej klasie pochodnej, gdy wywołuję ją bez wywoływania super (). Zredagowałem moje pytanie, aby było bardziej zrozumiałe :)
xhallix

W jakim środowisku to uruchamiasz?
Amit

6
Nie masz wyboru, jeśli rozszerzasz inną klasę, konstruktor musi najpierw wywołać super ().
Jonathan de M.

@JonathandeM. dziękuję, więc tak to chyba będzie w przyszłości?
xhallix

Odpowiedzi:


147

Zasady dla klas ES2015 (ES6) sprowadzają się zasadniczo do:

  1. W konstruktorze klasy podrzędnej thisnie można jej używać, dopóki nie superzostanie wywołana.
  2. Konstruktory klas ES6 MUSZĄ wywołać, superjeśli są podklasami, lub muszą jawnie zwrócić jakiś obiekt, aby zastąpić ten, który nie został zainicjowany.

Sprowadza się to do dwóch ważnych sekcji specyfikacji ES2015.

Sekcja 8.1.1.3.4 definiuje logikę decydującą o zawartości thisfunkcji. Ważną częścią klas jest to, że można thisbyć w "uninitialized"stanie, a gdy jest w tym stanie, próba użycia thisspowoduje zgłoszenie wyjątku.

Sekcja 9.2.2 , [[Construct]]w której zdefiniowano zachowanie funkcji wywoływanych przeznew lub super. Podczas wywoływania konstruktora klasy bazowej thisjest inicjowany w kroku # 8 programu [[Construct]], ale we wszystkich innych przypadkach thisjest niezainicjalizowany. Na końcu konstrukcji GetThisBindingjest wywoływana, więc jeśli supernie została jeszcze wywołana (a tym samym inicjalizacja this) lub nie został zwrócony jawny obiekt zastępczy, ostatnia linia wywołania konstruktora zgłosi wyjątek.


1
Czy możesz zasugerować sposób na dziedziczenie z klasy bez dzwonienia super()?
Bergi

4
Czy myślisz, że w ES6 można dziedziczyć z klasy bez wywoływania super()konstruktora?
Bergi

8
Dzięki za edycję - jest teraz; możesz zrobić, return Object.create(new.target.prototype, …)aby uniknąć wywoływania super konstruktora.
Bergi


1
Powinieneś dodać, co się stanie, jeśli pominięto konstruktor: zostanie użyty domyślny konstruktor zgodnie z MDN .
kleinfreund

11

Było wiele odpowiedzi i komentarzy stwierdzających, że super MUSI to być pierwszy wiersz w środku constructor. To jest po prostu złe. @loganfsmyth answer zawiera wymagane odniesienia do wymagań, ale sprowadza się to do:

extendsKonstruktor Inheriting ( ) musi wywołać superprzed użyciem thisi przed zwróceniem, nawet jeśli thisnie jest używany

Zobacz fragment poniżej (działa w Chrome ...), aby zobaczyć, dlaczego może mieć sens posiadanie instrukcji (bez użycia this) przed wywołaniem 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...)"otwórz konsolę chrom wywoływacza i kliknij „Run fragmencie kodu”: Uncaught ReferenceError: this is not defined. Jasne, możesz używać metod w konstruktorze wcześniej, super()ale nie możesz używać metod z klasy wcześniej!
marcel

którego nie możesz użyć thiswcześniej super()(Twój kod to udowodni) nie ma nic wspólnego ze specyfikacją natychmiastową, ale z implementacją javascript. Więc przed słowem „to” musisz nazwać „super”.
marcel

@marcel - Myślę, że mieliśmy dużo zamieszania. Stwierdziłem tylko (przez cały czas), że posiadanie STATEMENTÓW przed użyciem jest legalne super, a ty stwierdzałeś, że używanie tego jest nielegalne thisprzed zadzwonieniem super. Oboje mamy rację, po prostu się nie rozumieliśmy :-) (I ten wyjątek był zamierzony, aby pokazać, co nie jest legalne - nawet nazwałem nieruchomość „WillFail”)
Amit

9

Nowa składnia klasy es6 jest tylko inną notacją dla "starych" klas "es5" z prototypami. Dlatego nie można utworzyć wystąpienia określonej klasy bez ustawienia jej prototypu (klasy bazowej).

To jak umieszczanie sera na kanapce bez jej przyrządzania. Nie możesz też ułożyć sera przed zrobieniem kanapki, więc ...

... użycie thissłowa kluczowego przed wywołaniem superklasy with też super()jest niedozwolone.

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

Jeśli nie określisz konstruktora dla klasy bazowej, używana jest następująca definicja:

constructor() {}

W przypadku klas pochodnych używany jest następujący konstruktor domyślny:

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

EDYCJA: Znaleziono to w developer.mozilla.org:

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

Źródło


więc jeśli dobrze cię rozumiem, nie będę mógł użyć żadnej metody z klasy Character na mojej klasie Hero, gdy nie będę wywoływał super ()? Ale wydaje się, że nie jest to do końca poprawne, ponieważ mogę wywołać metody z klasy bazowej. więc myślę, że po prostu potrzebuję super, dzwoniąc do konstruktora
xhallix

1. W OP thisw ogóle nie jest używany. 2. JS nie jest kanapką, aw ES5 zawsze możesz użyć this, nawet przed wywołaniem jakiejkolwiek innej funkcji, którą lubisz (która może lub nie definiuje właściwości dodatku)
Amit

@amit 1. A teraz ja też nie mogę używać this?! 2. Moja klasa JS reprezentuje kanapkę, aw ES6 nie zawsze możesz jej użyć this. Próbuję tylko wyjaśnić klasy es6 (metaforą) i nikt nie potrzebuje takich destrukcyjnych / niepotrzebnych komentarzy.
marcel

@marcel Przepraszam za cynizm, ale: 1. chodziło o wprowadzenie nowego problemu, którego nie było w PO. 2 to zwrócenie uwagi, że Twoje roszczenie jest błędne (co nadal ma miejsce)
Amit

@marcel - Zobacz moją odpowiedź
Amit

4

Właśnie zarejestrowałem się, aby opublikować to rozwiązanie, ponieważ odpowiedzi tutaj nie satysfakcjonują mnie najmniej, ponieważ w rzeczywistości istnieje prosty sposób obejścia tego. Dostosuj wzorzec tworzenia klas, aby nadpisać logikę w metodzie podrzędnej, używając tylko konstruktora super, i przekaż argumenty konstruktorów do niego.

Tak jak w przypadku, nie tworzysz konstruktora w swoich podklasach jako takich, a jedynie odniesienie do metody, która jest nadpisana w odpowiedniej podklasie.

Oznacza to, że uwalniasz się od funkcji konstruktora narzuconej tobie i powstrzymujesz się od zwykłej metody - która może być nadpisana i nie wymusza super (), gdy pozwalasz sobie na wybór, czy, gdzie i jak chcesz wywołać super (w pełni opcjonalnie) np:

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

Twoje zdrowie!


2
Potrzebuję „wyjaśnienia, jakbym miał pięć lat” na ten temat.
Wydaje

@swyx: magia znajduje się wewnątrz konstruktora, gdzie „this” odnosi się do innego typu obiektu w zależności od tego, jaki typ obiektu tworzysz. Np. Jeśli konstruujesz nową DomainObservable, this.ObjectConstructor odwołuje się do innej metody, np. DomainObserveable.ObjectConstructor; natomiast jeśli tworzysz nową ArrayObservable, this.ObjectConstructor odwołuje się do ArrayObservable.ObjectConstructor.
cobberboy

Zobacz moją odpowiedź, zamieściłem dużo prostszy przykład
Michael Lewis

Całkowicie się zgadzam @swyx; ta odpowiedź robi o wiele za dużo ... tylko ją przejrzałem i już jestem zmęczony. Czuję się jak "wyjaśnij, jakbym miał pięć lat i naprawdę muszę się wysikać ..."
spb

4

Możesz pominąć super () w swojej podklasie, jeśli całkowicie pominiesz konstruktora w swojej podklasie. „Ukryty” domyślny konstruktor zostanie automatycznie dołączony do Twojej podklasy. Jeśli jednak włączysz konstruktor do swojej podklasy, w tym konstruktorze musi zostać wywołana funkcja super ().

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

Przeczytaj to, aby uzyskać więcej informacji.


2

Odpowiedź justyourimage jest najłatwiejsza, ale jego przykład jest trochę nadęty. Oto wersja ogólna:

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

Nie rozszerzaj rzeczywistości constructor(), po prostu użyj fałszywej _constructor()logiki tworzenia instancji.

Zwróć uwagę, że to rozwiązanie sprawia, że ​​debugowanie jest denerwujące, ponieważ musisz przejść do dodatkowej metody dla każdej instancji.


Tak, to jest zdecydowanie najłatwiejsza metoda i najjaśniejsza odpowiedź - pytanie, które zadaję brzmi… „Dlaczego, Boże, DLACZEGO!?”… Jest bardzo ważny powód, dla którego super () nie jest automatyczne. .. możesz chcieć przekazać określone parametry do swojej klasy bazowej, abyś mógł wykonać pewne przetwarzanie / logikę / myślenie przed utworzeniem wystąpienia klasy bazowej.
LFLFM

1

Próbować:

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


w tym przykładzie używasz również super () i jeśli ją opuścisz, zostanie zgłoszony wyjątek. Więc myślę, że nie można pominąć tego wywołania super () w tym przypadku
xhallix

Myślałem, że twoim celem nie jest wykonanie konstruktora rodzica, to właśnie robi ten kod. Nie możesz pozbyć się super podczas przedłużania.
Jonathan de M.

przepraszam, że wprowadzam w błąd :) Nie, chciałem tylko wiedzieć, czy naprawdę konieczne jest użycie super (), ponieważ zastanawiam się nad składnią, ponieważ w innych językach nie musimy wywoływać metody super podczas wywoływania konstruktora dla klasy pochodnej
xhallix

1
Według developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.więc powiedziałbym, że poczekaj na wydanie ES6
Jonathan de M.

3
„więc powiedziałbym, że poczekaj na wydanie ES6” --- już zostało wydane, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms

1

Polecam użycie OODK-JS, jeśli zamierzasz rozwijać następujące koncepcje 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);
});

1

Proste rozwiązanie: myślę, że nie ma potrzeby wyjaśniania.

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

Nie jestem pewien, dlaczego powyższy kod standardowy, i nie jestem dokładnie pewien, czy istnieją skutki uboczne tego podejścia. U mnie działa bez problemu.
MyUserInStackOverflow

1
Jeden problem: jeśli chcesz ponownie rozszerzyć swoją SubClass, będziesz musiał wbudować funkcję skipConstructor do każdego konstruktora SubClass
Michael Lewis

0

@Bergi wspomniałem new.target.prototype, ale szukałem konkretnego przykładu udowadniającego, że możesz uzyskać dostęp this(lub lepiej, odwołanie do obiektu, za pomocą którego tworzy kod klienta new, patrz poniżej) bez konieczności wywoływania super().

Rozmowa jest tania, pokaż mi kod ... Oto przykład:

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)

Który wyświetli:

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

Więc widać, że są skutecznie tworząc obiekt typu B(klasy dziecko), która jest również obiektem typu A(swojej klasie dominującej) oraz w ramach childMethod()dziecka Bmamy thisskierowaną do obiektu obj, który stworzyliśmy w B constructorz Object.create(new.target.prototype).

A wszystko to bez żadnej troski super.

Wykorzystuje to fakt, że w JS a constructormoże zwrócić zupełnie inny obiekt, gdy kod klienta konstruuje nową instancję z new.

Mam nadzieję, że to komuś pomoże.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.