Як розширити клас без використання супер в 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


Що ви маєте на увазі проблему сфери застосування? Ви отримуєте виняток (і де)?
Аміт

Я отримую очікування у своєму похідному класі, коли викликаю його, не викликаючи 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()?
Бергі

4
Як ви вважаєте, чи можливо в ES6 успадкувати від класу, не викликаючи super()конструктор?
Бергі

8
Дякую за редагування - саме зараз; ви можете зробити це, return Object.create(new.target.prototype, …)щоб уникнути виклику суперконструктора.
Бергі

2
@Maximus Дивіться, що таке "new.target"?
Бергі

1
Ви повинні додати, що станеться, якщо конструктор пропущено: Конструктор за замовчуванням буде використовуватися відповідно до MDN .
kleinfreund

11

Було багато відповідей та коментарів, в яких зазначалося, що super ОБОВ'ЯЗКОВО повинен бути перший рядок всередині constructor. Це просто неправильно. Відповідь @loganfsmyth містить обов'язкові посилання на вимоги, але вона зводиться до:

extendsКонструктор успадкування ( ) повинен викликати 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()але ви не можете використовувати методи з класу раніше!
marcel

що ви не можете використовувати thisраніше super()(ваш код доводить це) не має нічого спільного з специфікацією негайно, а з реалізацією javascript. Отже, ви повинні назвати "супер" перед "цим".
marcel

@marcel - Я думаю, у нас було багато плутанини. Я лише заявив (весь час), що перед використанням законно мати ЗВІДОМЛЕННЯsuper , і ви заявляли, що використовувати це незаконне thisперед тим, як дзвонити super. Ми обоє праві, просто не зрозуміли один одного :-) (І це виключення було навмисним, щоб показати те, що не є законним - я навіть назвав власність "WillFail")
Amit

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

EDIT: Знайдено це в 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 ()? Але це здається не зовсім коректним, тому що я можу викликати методи з базового класу. тому я думаю, що мені просто потрібно супер, коли дзвонить конструктору
xhallix

1. В ОП thisвзагалі не використовується. 2. JS - це не сендвіч, і в ES5 ви завжди можете користуватися this, навіть перед тим, як викликати будь-яку іншу функцію, яка вам подобається (яка може або не може визначити властивість додатку)
Amit

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

@marcel Мої вибачення за цинізм, але: 1. стосувався запровадження нової проблеми, яка не існувала в ОП. 2 - вказати на вашу увагу, що ваша претензія неправильна (що все-таки має місце)
Аміт

@marcel - Дивіться мою відповідь
Amit

4

Щойно зареєстрований, щоб розмістити це рішення, оскільки відповіді тут мене не задовольняють ні найменше, оскільки насправді існує простий спосіб цього. Налаштуйте свій шаблон створення класів, щоб перезаписати свою логіку в підметоді, використовуючи лише супер конструктор і передати йому аргументи конструкторів.

Так само як ви не створюєте конструктор у своїх підкласах, а лише посилання на метод, який переосмислений у відповідному підкласі.

Це означає, що ви позбавитесь від функціональних можливостей конструктора, накладених на вас, і утримаєтесь від звичайного методу - який можна переосмислити і не примусити супер (), якщо ви дозволяєте собі вибір, якщо, де і як ви хочете викликати супер (повністю необов'язково), наприклад:

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

@swyx: магія знаходиться всередині конструктора, де "це" стосується іншого типу об'єкта залежно від типу об'єкта, який ви створюєте. Наприклад, якщо ви створюєте новий DomainObservable, this.ObjectConstructor посилається на інший метод, тобто DomainObserveable.ObjectConstructor; тоді як якщо ви створюєте новий ArrayObservable, це.ObjectConstructor посилається на ArrayObservable.ObjectConstructor.
ковбой

Дивіться свою відповідь, я розмістив набагато простіший приклад
Майкл Льюїс

Я повністю згоден @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

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 (), і якщо ви залишите його, у вас викинуто виняток. Тому я вважаю, що неможливо пропустити цей супер () дзвінок у цьому випадку
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, якщо ви плануєте розробляти наступні концепції 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);
});

0

Просте рішення: я вважаю, що його ясна відсутність необхідності пояснювати.

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

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

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який ми створили в Bconstructor з Object.create(new.target.prototype).

І все це без турботи super взагалі.

Це використовує той факт, що в JS a constructorможе повернути зовсім інший об'єкт, коли код клієнта будує новий екземпляр ізnew .

Сподіваюся, що це комусь допоможе.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.