Как расширить класс без использования super в ES6?


98

Можно ли расширить класс в ES6 без вызова superметода для вызова родительского класса?

EDIT: вопрос может вводить в заблуждение. Это стандарт, по которому мы должны звонить, super()или я что-то упускаю?

Например:

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

Когда я не вызываю super()производный класс, у меня возникает проблема с областью видимости ->this is not defined

Я запускаю это с помощью iojs --harmony в v2.3.0


Что вы имеете в виду под проблемой масштаба? У вас есть исключение (и где)?
Amit

Я получаю ожидание в своем производном классе при его вызове без вызова super (). Я отредактировал свой вопрос, чтобы было понятнее :)
xhallix

В какой среде вы это используете?
Амит,

6
У вас нет выбора, если вы расширяете другой класс, конструктор должен сначала вызвать super ().
Джонатан де М.

@JonathandeM. спасибо, значит, так должно быть в будущем?
xhallix

Ответы:


147

Правила для классов ES2015 (ES6) в основном сводятся к:

  1. В конструкторе дочернего класса thisнельзя использовать до superвызова.
  2. Конструкторы классов ES6 ДОЛЖНЫ вызывать, superесли они являются подклассами, или они должны явно возвращать какой-либо объект, чтобы заменить тот, который не был инициализирован.

Это сводится к двум важным разделам спецификации ES2015.

Раздел 8.1.1.3.4 определяет логику, чтобы решить, что thisнаходится в функции. Важная часть для классов заключается в том, что они могут thisнаходиться в "uninitialized"состоянии, и когда в этом состоянии попытка использования thisвызовет исключение.

Раздел 9.2.2 , в [[Construct]]котором определяется поведение функций, вызываемых через newили super. При вызове конструктора базового класса thisинициализируется на шаге № 8 [[Construct]], но во всех остальных случаях thisне инициализируется. В конце конструкции GetThisBindingвызывается, поэтому, если он superеще не был вызван (таким образом, инициализирован this) или явный объект замены не был возвращен, последняя строка вызова конструктора вызовет исключение.


1
Не могли бы вы предложить способ наследования класса без вызова super()?
Bergi

4
Как вы думаете, возможно ли в ES6 наследование от класса без вызова super()конструктора?
Bergi

8
Спасибо за правку - прямо сейчас; вы можете сделать, return Object.create(new.target.prototype, …)чтобы избежать вызова суперконструктора.
Bergi


1
Вы должны добавить, что произойдет, если конструктор опущен: в соответствии с MDN будет использоваться конструктор по умолчанию .
kleinfreund

11

Было несколько ответов и комментариев, в которых говорилось, что super ДОЛЖНА быть первой строчкой внутри constructor. Это просто неправильно. Ответ @loganfsmyth содержит необходимые ссылки на требования, но они сводятся к следующему:

extendsКонструктор Inheriting ( ) должен вызывать superперед использованием thisи перед возвратом, даже если thisне используется

См. Фрагмент ниже (работает в Chrome ...), чтобы понять, почему может иметь смысл иметь операторы (без использования this) перед вызовом 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...)"откройте консоль разработчика Chrome и нажмите на кнопку «Выполнить фрагмент кода»: Uncaught ReferenceError: this is not defined. Конечно, вы можете использовать методы в конструкторе раньше, super()но вы не можете использовать методы из класса раньше!
Марсель

то, что вы не можете использовать thisраньше super()(ваш код доказывает это), не имеет ничего общего со спецификацией непосредственно, а с реализацией javascript. Итак, вы должны назвать «супер» перед «этим».
Марсель

@marcel - Думаю, мы сильно запутались. Я только заявлял (все время), что законно иметь ЗАЯВЛЕНИЯ перед использованием super, а вы заявляли, что использовать thisперед вызовом незаконно super. Мы оба правы, просто не понимали друг друга :-) (И это исключение было намеренным, чтобы показать, что не является законным - я даже назвал свойство WillFail)
Амит

9

Синтаксис нового класса es6 - это всего лишь другая нотация для «старых» классов es5 с прототипами. Следовательно, вы не можете создать конкретный класс, не задав его прототип (базовый класс).

Это все равно, что положить сыр на бутерброд, не приготовив его. Также нельзя класть сыр перед приготовлением бутерброда, так что ...

... использование thisключевого слова перед вызовом суперкласса также 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";
    }
}

Если вы не укажете конструктор для базового класса, используется следующее определение:

constructor() {}

Для производных классов используется следующий конструктор по умолчанию:

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

РЕДАКТИРОВАТЬ: нашел это developer.mozilla.org:

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

Источник


Итак, если я правильно вас понимаю, я не смогу использовать какой-либо метод из класса Character в моем классе Hero, если не вызываю super ()? Но это кажется не совсем правильным, потому что я могу вызывать методы из базового класса. так что я думаю, мне просто нужен super при вызове конструктора
xhallix

1. В ОП thisвообще не используется. 2. JS - это не бутерброд, и в ES5 вы всегда можете использовать this, даже перед вызовом любой другой функции, которая вам нравится (которая может определять или не определять свойство дополнения)
Амит,

@amit 1. А теперь я тоже не могу использовать this?! 2. Мой класс JS представляет собой бутерброд, и в ES6 вы не всегда можете его использовать this. Я просто пытаюсь объяснить классы es6 (метафорой), и никому не нужны такие деструктивные / ненужные комментарии.
Марсель

@marcel Приношу свои извинения за цинизм, но: 1. Речь идет о введении новой проблемы, которой не было в ОП. 2, чтобы обратить ваше внимание на то, что ваше утверждение неверно (что до сих пор так)
Амит

@marcel - См. мой ответ
Амит

4

Просто зарегистрировался, чтобы опубликовать это решение, поскольку ответы здесь меня нисколько не удовлетворяют, поскольку на самом деле есть простой способ обойти это. Отрегулируйте шаблон создания класса, чтобы перезаписать вашу логику в подметоде, используя только суперконструктор, и перенаправить ему аргументы конструктора.

Как и в случае, вы не создаете конструктор в своих подклассах как таковых, а только ссылку на метод, который переопределяется в соответствующем подклассе.

Это означает, что вы освобождаете себя от навязываемой вам функциональности конструктора и воздерживаетесь от обычного метода, который может быть переопределен и не применяет super (), если вы позволяете себе выбирать, где и как вы хотите вызвать super (полностью необязательно) например:

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

ура!


2
мне нужно «объясни, как будто мне пять» на этот вопрос .. я чувствую, что это очень глубокий ответ, но сложный и
поэтому

@swyx: магия находится внутри конструктора, где «this» относится к другому типу объекта в зависимости от того, какой тип объекта вы создаете. Например, если вы создаете новый объект DomainObservable, this.ObjectConstructor относится к другому методу, например, DomainObserveable.ObjectConstructor; тогда как если вы создаете новый ArrayObservable, this.ObjectConstructor ссылается на ArrayObservable.ObjectConstructor.
cobberboy

Смотрите мой ответ, я опубликовал гораздо более простой пример
Майкл Льюис

Полностью согласен @swyx; этот ответ слишком важен ... я только просмотрел его и уже устал. Я чувствую, что «объясни, как будто мне пять, и мне действительно нужно пописать ...»
spb

4

Вы можете опустить super () в своем подклассе, если вы полностью опустите конструктор в своем подклассе. «Скрытый» конструктор по умолчанию будет автоматически включен в ваш подкласс. Однако, если вы все же включаете конструктор в свой подкласс, в этом конструкторе должен вызываться 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

Прочтите это для получения дополнительной информации.


2

Ответ justyourimage - самый простой способ, но его пример немного раздут. Вот общая версия:

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

Не расширяйте реальное constructor(), просто используйте подделку _constructor()для логики создания экземпляра.

Обратите внимание, что это решение делает отладку раздражающей, потому что вам нужно использовать дополнительный метод для каждого экземпляра.


Да, это, безусловно, самый простой метод и самый ясный ответ - вопрос, который я задаю ... «Почему, Боже, ПОЧЕМУ!?» ... Существует очень веская причина, по которой super () не работает автоматически. .. вы можете захотеть передать определенные параметры вашему базовому классу, чтобы вы могли выполнить некоторую обработку / логику / размышления перед созданием экземпляра базового класса.
LFLFM 08

1

Пытаться:

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


в этом примере вы также используете super (), и если вы оставите его, вы получите исключение. Поэтому я думаю, что в этом случае невозможно пропустить этот вызов super ()
xhallix

Я думал, что ваша цель не в том, чтобы выполнить родительский конструктор, а в этом коде. От супер при расширении не избавишься.
Джонатан де М.

извините за то, что вводил в заблуждение :) Нет, я просто хотел знать, действительно ли нужно использовать super (), поскольку меня интересует синтаксис, потому что в других языках нам не нужно вызывать супер-метод при вызове конструктора для производного класса
xhallix

1
Согласно developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.поэтому я бы сказал, дождитесь выпуска ES6
Джонатан де М.

3
«поэтому я бы сказал, дождитесь релиза ES6» --- он уже был выпущен, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms

1

Я бы рекомендовал использовать OODK-JS, если вы собираетесь разрабатывать следующие концепции ООП.

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

Простое решение: я думаю, что объяснения очевидны.

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 06

1
Одна проблема: если вы хотите снова расширить свой подкласс, вам придется встроить функцию skipConstructor в каждый конструктор
Майкл Льюис,

0

@Bergi упомянул new.target.prototype, но я искал конкретный пример, доказывающий, что вы можете получить доступ this(или, лучше, ссылку на объект, с которым создается клиентский код new, см. Ниже) без необходимости вызыватьsuper() вообще .

Обсуждение дешево, покажите код ... Итак, вот пример:

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)

Что выведет:

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

Итак, вы можете видеть, что мы эффективно создаем объект типа B(дочерний класс), который также является объектом типа A(его родительский класс), и внутри childMethod()дочернего Bмы thisуказываем на объект, objкоторый мы создали в B constructorс помощью Object.create(new.target.prototype).

И все это совершенно безразлично super.

Это использует тот факт, что в JS a constructorможет возвращать совершенно другой объект, когда клиентский код создает новый экземпляр с new.

Надеюсь, это кому-то поможет.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.