Como estender uma aula sem ter que usar super no ES6?

98

É possível estender uma classe no ES6 sem chamar o supermétodo para invocar a classe pai?

EDIT: A pergunta pode ser enganosa. É o padrão que temos que ligar super()ou estou faltando alguma coisa?

Por exemplo:

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 não estou chamando super()a classe derivada, estou tendo um problema de escopo ->this is not defined

Estou executando isso com iojs --harmony na v2.3.0

xhallix
fonte
O que você quer dizer com problema de escopo? Você está recebendo uma exceção (e onde)?
Amit
Estou recebendo a expectativa em minha classe derivada ao invocá-la sem chamar super ().
Editei
Em que ambiente você está executando isso?
Amit
6
Você não tem escolha se estender outra classe, o construtor deve primeiro chamar super ().
Jonathan de M.
@JonathandeM. obrigado, então é assim que deve ser no futuro, eu acho?
xhallix

Respostas:

147

As regras para as classes ES2015 (ES6) basicamente se resumem a:

  1. Em um construtor de classe filha, thisnão pode ser usado até que superseja chamado.
  2. Os construtores da classe ES6 DEVEM chamar superse forem subclasses, ou devem retornar explicitamente algum objeto para substituir aquele que não foi inicializado.

Isso se resume a duas seções importantes da especificação ES2015.

A seção 8.1.1.3.4 define a lógica para decidir o que thisestá na função. A parte importante para as classes é que é possível thisestar em um "uninitialized"estado e, quando nesse estado, tentar usar thislançará uma exceção.

Seção 9.2.2 , [[Construct]]que define o comportamento das funções chamadas via newou super. Ao chamar um construtor de classe base, thisé inicializado na etapa 8 de [[Construct]], mas para todos os outros casos, thisnão é inicializado. No final da construção, GetThisBindingé chamado, portanto, se superainda não tiver sido chamado (inicializando assim this), ou um objeto de substituição explícito não for retornado, a linha final da chamada do construtor lançará uma exceção.

loganfsmyth
fonte
1
Você poderia sugerir uma maneira de herdar de uma classe sem chamar super()?
Bergi
4
Você acha que é possível no ES6 herdar de uma classe sem chamar super()o construtor?
Bergi
8
Obrigado pela edição - está aí agora; você pode fazer return Object.create(new.target.prototype, …)para evitar chamar o super construtor.
Bergi
2
@Maximus See O que é “new.target”?
Bergi
1
Você deve adicionar o que acontece se o construtor for omitido: Um construtor padrão será usado de acordo com o MDN .
Kleinfreund
11

Houve várias respostas e comentários afirmando que super DEVE ser a primeira linha dentro constructor. Isso está simplesmente errado. A resposta @loganfsmyth tem as referências exigidas dos requisitos, mas se resume a:

O extendsconstrutor Inheriting ( ) deve chamar superantes de usar thise antes de retornar, mesmo se thisnão for usado

Veja o fragmento abaixo (funciona no Chrome ...) para ver por que pode fazer sentido ter instruções (sem usar this) antes de chamar 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
fonte
2
"See fragment below (works in Chrome...)"abra o console do desenvolvedor Chrome e clique em "código Run trecho": Uncaught ReferenceError: this is not defined. Claro, você pode usar métodos no construtor antes, super()mas não pode usar métodos da classe antes!
março de
que você não pode usar thisantes super()(seu código prova isso) não tem nada a ver com a especificação imediata, mas com a implementação de javascript. Então, você tem que chamar 'super' antes de 'isso'.
março de
@marcel - Acho que tivemos muita confusão. Eu estava apenas afirmando (o tempo todo) que é legal ter STATEMENTS antes de usar super, e você estava afirmando que é ilegal usar thisantes de ligar super. Estamos ambos certos, apenas não nos entendíamos :-) (E essa exceção foi intencional, para mostrar o que não é legal - até chamei a propriedade de 'WillFail')
Amit
9

A nova sintaxe da classe es6 é apenas uma outra notação para as "antigas" classes es5 "com protótipos. Portanto, você não pode instanciar uma classe específica sem definir seu protótipo (a classe base).

É como colocar queijo no sanduíche sem prepará-lo. Também não se pode colocar queijo antes de fazer o sanduíche, então ...

... usar a thispalavra-chave antes de chamar a superclasse com super()também não é permitido.

// 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 você não especificar um construtor para uma classe base, a seguinte definição será usada:

constructor() {}

Para classes derivadas, o seguinte construtor padrão é usado:

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

EDIT: Encontrado em 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

marcel
fonte
então, se entendi bem, não poderei usar nenhum método da classe Character em minha classe Hero, quando não invocar super ()? Mas isso parece não estar totalmente correto, porque posso chamar os métodos da classe base. então eu acho que só preciso de super ao chamar o construtor
xhallix
1. No OP, thisnão é usado. 2. JS não é um sanduíche e no ES5 você sempre pode usar this, mesmo antes de chamar qualquer outra função que desejar (que pode ou não definir a propriedade de suplemento)
Amit
@amit 1. E agora não posso usar thistambém ?! 2. Minha classe JS representa um sanduíche, e no ES6 você nem sempre pode usar this. Estou apenas tentando explicar as classes es6 (com uma metáfora), e ninguém precisa desses comentários destrutivos / desnecessários.
março de
@marcel Minhas desculpas pelo cinismo, mas: 1. era sobre a introdução de um novo problema que não existia no OP. 2 é para chamar a atenção de que sua afirmação está errada (o que ainda é o caso)
Amit
@marcel - Veja minha resposta
Amit
4

Só me inscrevi para postar essa solução pois as respostas aqui não me satisfazem minimamente, pois na verdade existe uma maneira simples de contornar isso. Ajuste seu padrão de criação de classe para sobrescrever sua lógica em um sub-método enquanto usa apenas o super construtor e encaminhe os argumentos do construtor para ele.

Como em, você não cria um construtor em suas subclasses per se, mas apenas faz referência a um método que é sobrescrito na respectiva subclasse.

Isso significa que você se libertou da funcionalidade do construtor imposta a você e se absteve de um método regular - que pode ser substituído e não impõe super () ao se permitir a escolha se, onde e como deseja chamar super (totalmente opcional) por exemplo:

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

Felicidades!

Justyourimage
fonte
2
eu preciso de um "explique como se eu tivesse cinco anos" sobre este .. sinto que esta é uma resposta muito profunda, mas complicada e, portanto, ignorada
swyx
@swyx: a mágica está dentro do construtor, onde 'this' se refere a um tipo diferente de objeto dependendo do tipo de objeto que você está criando. Por exemplo, se você estiver construindo um novo DomainObservable, this.ObjectConstructor se refere a um método diferente, ou seja, DomainObserveable.ObjectConstructor; enquanto se você estiver construindo um novo ArrayObservable, this.ObjectConstructor se refere a ArrayObservable.ObjectConstructor.
cobberboy
Veja minha resposta, postei um exemplo muito mais simples
Michael Lewis
Eu concordo totalmente com @swyx; esta resposta está fazendo demais ... eu só dei uma olhada e já estou cansado. Estou me sentindo como "explique como se eu tivesse cinco anos E realmente preciso fazer xixi ..."
spb
4

Você pode omitir super () em sua subclasse, se omitir totalmente o construtor em sua subclasse. Um construtor padrão 'oculto' será incluído automaticamente em sua subclasse. No entanto, se você incluir o construtor em sua subclasse, super () deve ser chamado nesse construtor.

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

Leia isto para mais informações.

Chong Lip Phang
fonte
2

A resposta por justyourimage é a maneira mais fácil, mas seu exemplo é um pouco inchado. Aqui está a versão genérica:

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

Não estenda o real constructor(), apenas use o falso _constructor()para a lógica de instanciação.

Observe que esta solução torna a depuração irritante porque você precisa adotar um método extra para cada instanciação.

Michael Lewis
fonte
Sim, este é de longe o método mais fácil e a resposta mais clara - a pergunta que eu faço é ... "Por que, Deus, POR QUÊ!?" ... Há uma razão muito válida pela qual super () não é automático. .. você pode querer passar parâmetros específicos para sua classe base, então você pode fazer algum processamento / lógica / pensamento antes de instanciar a classe base.
LFLFM
1

Experimentar:

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.
fonte
neste exemplo, você também usa super () e, se deixá-lo, terá uma exceção lançada. Então eu acho que não é possível omitir essa chamada super () neste caso
xhallix
Achei que seu objetivo não era executar o construtor pai, é isso que esse código faz. Você não pode se livrar do super ao estender.
Jonathan de M.
desculpe por ser enganoso :) Não, eu só queria saber se é realmente necessário usar super () já que me pergunto sobre a sintaxe porque em outras linguagens não temos que invocar o método super ao chamar o construtor para a classe derivada
xhallix
1
De acordo com developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.então eu diria que aguarde o lançamento do ES6
Jonathan de M.
3
"então, eu diria que espere pelo lançamento do ES6" --- ele já foi lançado, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms
1

Eu recomendaria usar OODK-JS se você pretende desenvolver os seguintes conceitos 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);
});
OBDM
fonte
0

Solução simples: acho que não há necessidade de explicação.

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
fonte
Não sei por que todo o código clichê acima, e não tenho certeza se há efeitos colaterais para essa abordagem. Funciona para mim sem problemas.
MyUserInStackOverflow
1
Um problema: se você quiser estender sua SubClasse novamente, você terá que construir o recurso skipConstructor no construtor de cada SubClass
Michael Lewis
0

@Bergi mencionou new.target.prototype, mas eu estava procurando um exemplo concreto provando que você pode acessar this(ou melhor, a referência ao objeto com o qual o código do cliente está criando new, veja abaixo) sem ter que chamar super().

Falar é barato, mostre-me o código ... Então, aqui está um exemplo:

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)

O que resultará em:

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

Assim você pode ver que estamos efetivamente criando um objeto do tipo B(a classe filha), que também é um objeto do tipo A(sua classe pai) e no childMethod()da criança B, temos thisapontando para o objeto objque criamos no B de constructorcom Object.create(new.target.prototype).

E tudo isso sem se preocupar com supernada.

Isso aproveita o fato de que em JS a constructorpode retornar um objeto completamente diferente quando o código do cliente constrói uma nova instância com new.

Espero que isso ajude alguém.

tonix
fonte