¿Cómo extender una clase sin tener que usar super en ES6?


98

¿Es posible extender una clase en ES6 sin llamar al supermétodo para invocar la clase principal?

EDITAR: La pregunta puede ser engañosa. ¿Es el estándar al que tenemos que llamar super()o me falta algo?

Por ejemplo:

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

Cuando no llamo super()a la clase derivada, tengo un problema de alcance ->this is not defined

Estoy ejecutando esto con iojs --harmony en v2.3.0


¿A qué te refieres con problema de alcance? ¿Está recibiendo una excepción (y dónde)?
Amit

Obtengo la expectativa en mi clase derivada al invocarla sin llamar a super (). Edité mi pregunta para que quede más clara :)
xhallix

¿En qué entorno estás ejecutando esto?
Amit

6
No tiene otra opción si extiende otra clase, el constructor debe llamar primero a super ().
Jonathan de M.

@JonathandeM. gracias, así que así es como se supone que será en el futuro, ¿supongo entonces?
xhallix

Respuestas:


147

Las reglas para las clases de ES2015 (ES6) básicamente se reducen a:

  1. En un constructor de clases secundarias, thisno se puede usar hasta que superse llame.
  2. Los constructores de la clase ES6 DEBEN llamar supersi son subclases, o deben devolver explícitamente algún objeto para tomar el lugar del que no se inicializó.

Esto se reduce a dos secciones importantes de la especificación ES2015.

La sección 8.1.1.3.4 define la lógica para decidir qué thishay en la función. La parte importante para las clases es que es posible thisestar en un "uninitialized"estado, y cuando se está en este estado, intentar usar thisarrojará una excepción.

Sección 9.2.2 , [[Construct]]que define el comportamiento de funciones llamadas vía newo super. Cuando se llama a un constructor de clase base, thisse inicializa en el paso # 8 de [[Construct]], pero para todos los demás casos, thisno se inicializa. Al final de la construcción, GetThisBindingse llama, por lo que si superaún no se ha llamado (por lo tanto, se está inicializando this), o no se ha devuelto un objeto de reemplazo explícito, la línea final de la llamada al constructor arrojará una excepción.


1
¿Podría sugerir una forma de heredar de una clase sin llamar super()?
Bergi

4
¿Crees que es posible en ES6 heredar de una clase sin llamar super()al constructor?
Bergi

8
Gracias por la edición, está ahí ahora; puede hacer return Object.create(new.target.prototype, …)para evitar llamar al superconstructor.
Bergi


1
Debe agregar lo que sucede si se omite el constructor: Se usará un constructor predeterminado de acuerdo con MDN .
kleinfreund

11

Ha habido múltiples respuestas y comentarios indicando que super DEBE ser la primera línea adentro constructor. Eso es simplemente incorrecto. La respuesta de @loganfsmyth tiene las referencias requeridas de los requisitos, pero se reduce a:

El extendsconstructor Inheriting ( ) debe llamar superantes de usar thisy antes de regresar, incluso si thisno se usa

Vea el fragmento a continuación (funciona en Chrome ...) para ver por qué podría tener sentido tener declaraciones (sin usar this) antes de llamar 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...)"abrir la consola para desarrolladores de Chrome y haga clic en "Ejecutar código de fragmento": Uncaught ReferenceError: this is not defined. Claro, puedes usar métodos en el constructor antes, ¡ super()pero no puedes usar métodos de la clase antes!
marcel

que no puede usar thisantes super()(su código lo prueba) no tiene nada que ver con la especificación de inmediato, sino con la implementación de javascript. Entonces, debes llamar a 'super' antes de 'esto'.
marcel

@marcel - Creo que tuvimos mucha confusión. Solo dije (todo el tiempo) que es legal tener STATEMENTS antes de usar super, y tú estabas diciendo que es ilegal usarlo thisantes de llamar super. Ambos tenemos razón, simplemente no nos entendimos :-) (Y esa excepción fue intencional, para mostrar lo que no es legal, incluso llamé a la propiedad 'WillFail')
Amit

9

La nueva sintaxis de la clase es6 es sólo otra notación para las "viejas" clases "es5" con prototipos. Por lo tanto, no puede crear una instancia de una clase específica sin establecer su prototipo (la clase base).

Eso es como poner queso en tu sándwich sin hacerlo. Además, no puedes poner queso antes de hacer el sándwich, así que ...

... tampoco se permite usar una thispalabra clave antes de llamar a la superclase con super().

// 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 no especifica un constructor para una clase base, se usa la siguiente definición:

constructor() {}

Para las clases derivadas, se utiliza el siguiente constructor predeterminado:

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

EDITAR: Encontré esto en developer.mozilla.org:

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

Fuente


así que si te entiendo correctamente, no podré usar ningún método de la clase Character en mi clase Hero, cuando no invoco super ()? Pero esto parece no ser del todo correcto, porque puedo llamar a los métodos desde la clase base. así que supongo que solo necesito super cuando llamo al constructor
xhallix

1. En el OP, thisno se usa en absoluto. 2. JS no es un sándwich, y en ES5 siempre puede usarlo this, incluso antes de llamar a cualquier otra función que desee (que podría o no definir la propiedad del suplemento)
Amit

@amit 1. ¡¿Y ahora no puedo usar thistambién ?! 2. Mi clase JS representa un sándwich y en ES6 no siempre se puede usar this. Solo estoy tratando de explicar las clases de es6 (con una metáfora), y nadie necesita comentarios tan destructivos / innecesarios.
marcel

@marcel Mis disculpas por el cinismo, pero: 1. Se trataba de introducir un nuevo problema que no existía en el OP. 2 es señalar su atención de que su afirmación es incorrecta (que sigue siendo el caso)
Amit

@marcel - Ver mi respuesta
Amit

4

Me acabo de registrar para publicar esta solución, ya que las respuestas aquí no me satisfacen en lo más mínimo, ya que en realidad hay una forma simple de evitar esto. Ajuste su patrón de creación de clases para sobrescribir su lógica en un submétodo mientras usa solo el superconstructor y le envía los argumentos del constructor.

Como en, no crea un constructor en sus subclases per se, sino solo una referencia a un método que se reemplaza en la subclase respectiva.

Eso significa que se libera de la funcionalidad de constructor que se le impone y se abstiene de usar un método regular , que puede ser anulado y no impone super () si se permite elegir si, dónde y cómo desea llamar a super (completamente opcional) por ejemplo:

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

¡salud!


2
Necesito un "explica como si tuviera cinco" en este caso ... siento que esta es una respuesta muy profunda pero complicada y, por lo tanto, ignorada
swyx

@swyx: la magia está dentro del constructor, donde 'esto' se refiere a un tipo diferente de objeto dependiendo del tipo de objeto que estés creando. Por ejemplo, si está construyendo un nuevo DomainObservable, this.ObjectConstructor se refiere a un método diferente, es decir, DomainObserveable.ObjectConstructor; mientras que si está construyendo un nuevo ArrayObservable, this.ObjectConstructor se refiere a ArrayObservable.ObjectConstructor.
cobberboy

Vea mi respuesta, publiqué un ejemplo mucho más simple
Michael Lewis

Estoy completamente de acuerdo @swyx; esta respuesta está haciendo demasiado ... solo la hojeé y ya estoy cansado. Me siento como "explique como si tuviera cinco años Y realmente tuviera que orinar ..."
spb

4

Puede omitir super () en su subclase, si omite el constructor por completo en su subclase. Un constructor predeterminado 'oculto' se incluirá automáticamente en su subclase. Sin embargo, si incluye el constructor en su subclase, debe llamarse super () en ese constructor.

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

Lea esto para obtener más información.


2

La respuesta con justyourimage es la forma más fácil, pero su ejemplo es un poco hinchado. Aquí está la versión 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();
    }
}

No extiendas lo real constructor(), solo usa lo falso _constructor()para la lógica de creación de instancias.

Tenga en cuenta que esta solución hace que la depuración sea molesta porque debe ingresar a un método adicional para cada instanciación.


Sí, este es, con mucho, el método más fácil y la respuesta más clara: la pregunta que hago es ... "¿Por qué, Dios, POR QUÉ?" ... Hay una razón muy válida por la que super () no es automático. .. es posible que desee pasar parámetros específicos a su clase base, para que pueda hacer algo de procesamiento / lógica / pensamiento antes de instanciar la clase base.
LFLFM

1

Tratar:

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


en este ejemplo también usa super () y si lo deja, se lanza una excepción. Así que creo que no es posible omitir esta llamada super () en este caso
xhallix

Pensé que su objetivo no era ejecutar el constructor principal, eso es lo que hace este código. No puedes deshacerte de super al extender.
Jonathan de M.

perdón por ser engañoso :) No, solo quería saber si es realmente necesario usar super () ya que me pregunto acerca de la sintaxis porque en otros lenguajes no tenemos que invocar el método super cuando llamamos al constructor para la clase derivada
xhallix

1
Según developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.entonces diría que espere el lanzamiento de ES6
Jonathan de M.

3
"entonces diría que espere el lanzamiento de ES6" --- ya fue lanzado, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms

1

Recomendaría usar OODK-JS si tiene la intención de desarrollar los siguientes conceptos de programación orientada a objetos.

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

Solución simple: creo que está claro que no hay necesidad de explicación.

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

No estoy seguro de por qué todo el código repetitivo anterior, y no estoy exactamente seguro de si este enfoque tiene efectos secundarios. Funciona para mí sin ningún problema.
MyUserInStackOverflow

1
Un problema: si desea extender su SubClass nuevamente, tendrá que construir la función skipConstructor en cada constructor de SubClass
Michael Lewis

0

@Bergi mencionó new.target.prototype, pero estaba buscando un ejemplo concreto que demuestre que puede acceder this(o mejor, la referencia al objeto con el que está creando el código del cliente new, ver más abajo) sin tener que llamar super()en absoluto.

Hablar es barato, enséñame el código ... Así que aquí tienes un ejemplo:

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)

Que dará como resultado:

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

Entonces, puede ver que estamos creando efectivamente un objeto de tipo B(la clase secundaria) que también es un objeto de tipo A(su clase principal) y dentro childMethod()del elemento secundario Btenemos thisapuntando al objeto objque creamos en B constructorcon Object.create(new.target.prototype).

Y todo esto sin importarle supernada.

Esto aprovecha el hecho de que en JS a constructorpuede devolver un objeto completamente diferente cuando el código del cliente construye una nueva instancia con new.

Espero que esto ayude a alguien.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.