Bagaimana cara memperluas kelas tanpa harus menggunakan super di ES6?


98

Apakah mungkin untuk memperluas kelas di ES6 tanpa memanggil supermetode untuk memanggil kelas induk?

EDIT: Pertanyaannya mungkin menyesatkan. Apakah ini standar yang harus kita panggil super()atau apakah saya melewatkan sesuatu?

Sebagai contoh:

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

Ketika saya tidak memanggil super()kelas turunan, saya mendapatkan masalah lingkup ->this is not defined

Saya menjalankan ini dengan iojs --harmony di v2.3.0


Apa yang Anda maksud masalah ruang lingkup? Apakah Anda mendapatkan pengecualian (dan di mana)?
Amit

Saya mendapatkan ekspektasi di kelas turunan saya saat menjalankannya tanpa memanggil super (). Saya mengedit pertanyaan saya untuk membuatnya lebih jelas :)
xhallix

Di lingkungan apa Anda menjalankan ini?
Amit

6
Anda tidak punya pilihan jika Anda memperluas kelas lain yang konstruktor harus terlebih dahulu memanggil super ().
Jonathan de M.

@Tokopedia terima kasih, jadi begini seharusnya di masa depan, ya?
xhallix

Jawaban:


147

Aturan untuk kelas ES2015 (ES6) pada dasarnya adalah:

  1. Dalam konstruktor kelas anak, thistidak dapat digunakan sampai superdipanggil.
  2. Konstruktor kelas ES6 HARUS memanggil superjika mereka adalah subkelas, atau mereka harus secara eksplisit mengembalikan beberapa objek untuk menggantikan objek yang tidak diinisialisasi.

Ini tergantung pada dua bagian penting dari spesifikasi ES2015.

Bagian 8.1.1.3.4 mendefinisikan logika untuk memutuskan apa yang thisada di dalam fungsi. Bagian penting untuk kelas adalah dimungkinkan untuk thisberada dalam "uninitialized"keadaan, dan ketika dalam keadaan ini, mencoba menggunakan thisakan memunculkan pengecualian.

Bagian 9.2.2 , [[Construct]]yang mendefinisikan perilaku fungsi yang dipanggil melalui newatau super. Saat memanggil konstruktor kelas dasar, thisdiinisialisasi pada langkah # 8 [[Construct]], tetapi untuk semua kasus lainnya, thistidak diinisialisasi. Pada akhir konstruksi, GetThisBindingdipanggil, jadi jika superbelum dipanggil (dengan demikian menginisialisasi this), atau objek pengganti eksplisit tidak dikembalikan, baris terakhir dari panggilan konstruktor akan memunculkan pengecualian.


1
Bisakah Anda menyarankan cara untuk mewarisi dari kelas tanpa menelepon super()?
Bergi

4
Apakah menurut Anda mungkin di ES6 mewarisi dari kelas tanpa memanggil super()konstruktor?
Bergi

8
Terima kasih atas pengeditannya - sekarang ada di sana; Anda dapat melakukannya return Object.create(new.target.prototype, …)untuk menghindari pemanggilan konstruktor super.
Bergi

2
@Maximus Lihat Apa itu "new.target"?
Bergi

1
Anda harus menambahkan apa yang terjadi jika konstruktor dihilangkan: Konstruktor default akan digunakan sesuai dengan MDN .
kleinfreund

11

Ada banyak jawaban dan komentar yang menyatakan bahwa super HARUS menjadi baris pertama di dalamnya constructor. Itu salah. @loganfsmyth jawaban memiliki referensi persyaratan yang diperlukan, tetapi intinya adalah:

extendsKonstruktor Inheriting ( ) harus memanggil supersebelum menggunakan thisdan sebelum kembali bahkan jika thistidak digunakan

Lihat fragmen di bawah (berfungsi di Chrome ...) untuk mengetahui mengapa masuk akal untuk memiliki pernyataan (tanpa menggunakan this) sebelum memanggil 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...)"membuka konsol pengembang krom dan klik pada "kode Run potongan": Uncaught ReferenceError: this is not defined. Tentu, Anda dapat menggunakan metode dalam konstruktor sebelumnya, super()tetapi Anda tidak dapat menggunakan metode dari kelas sebelumnya!
marcel

yang tidak dapat Anda gunakan thissebelumnya super()(kode Anda membuktikannya) tidak ada hubungannya dengan spesifikasi secara langsung, tetapi dengan implementasi javascript. Jadi, Anda harus memanggil 'super' sebelum 'ini'.
marcel

@marcel - Saya pikir kami memiliki banyak kebingungan. Saya hanya menyatakan (selama ini) bahwa adalah legal untuk memiliki PERNYATAAN sebelum menggunakan super, dan Anda menyatakan bahwa penggunaan thissebelum menelepon adalah ilegal super. Kami berdua benar, hanya tidak memahami satu sama lain :-) (Dan pengecualian itu disengaja, untuk menunjukkan apa yang tidak legal - saya bahkan menamai properti itu 'WillFail')
Amit

9

Sintaks kelas es6 baru hanyalah notasi lain untuk "lama" kelas "es5" dengan prototipe. Oleh karena itu, Anda tidak dapat membuat instance kelas tertentu tanpa menyetel prototipe-nya (kelas dasar).

Itu seperti meletakkan keju di atas sandwich Anda tanpa membuatnya. Juga Anda tidak bisa menaruh keju sebelum membuat sandwich, jadi ...

... menggunakan thiskata kunci sebelum memanggil kelas super dengan super()juga tidak diperbolehkan.

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

Jika Anda tidak menentukan konstruktor untuk kelas dasar, definisi berikut digunakan:

constructor() {}

Untuk kelas turunan, konstruktor default berikut digunakan:

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

EDIT: Temukan ini di developer.mozilla.org:

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

Sumber


jadi jika saya memahami Anda dengan benar, saya tidak akan dapat menggunakan metode apa pun dari Karakter kelas pada Pahlawan kelas saya, jika tidak menggunakan super ()? Tetapi ini tampaknya tidak sepenuhnya benar, karena saya dapat memanggil metode dari kelas dasar. jadi saya rasa saya hanya perlu super ketika memanggil konstruktor
xhallix

1. Di OP, thistidak digunakan sama sekali. 2. JS bukan sandwich, dan di ES5 Anda selalu dapat menggunakannya this, bahkan sebelum memanggil fungsi lain yang Anda suka (yang mungkin atau mungkin tidak mendefinisikan properti suplemen)
Amit

@amit 1. Dan sekarang saya tidak bisa menggunakan thisjuga ?! 2. Kelas JS saya mewakili sandwich, dan di ES6 Anda tidak selalu bisa menggunakannya this. Saya hanya mencoba menjelaskan kelas es6 (dengan metafora), dan tidak ada yang membutuhkan komentar yang merusak / tidak perlu.
marcel

@marcel Saya minta maaf atas sinisme tersebut, tapi: 1. tentang memperkenalkan masalah baru yang tidak ada di OP. 2 adalah untuk menunjukkan perhatian Anda bahwa klaim Anda salah (Yang masih terjadi)
Amit

@marcel - Lihat jawaban saya
Amit

4

Baru saja mendaftar untuk memposting solusi ini karena jawaban di sini sama sekali tidak memuaskan saya karena sebenarnya ada cara sederhana untuk melakukannya. Sesuaikan pola pembuatan kelas Anda untuk menimpa logika Anda dalam sub-metode saat hanya menggunakan konstruktor super dan meneruskan argumen konstruktor ke sana.

Seperti halnya Anda, jangan membuat konstruktor di subkelas Anda sendiri tetapi hanya merujuk ke metode yang diganti di subkelas masing-masing.

Itu berarti Anda membebaskan diri dari fungsionalitas konstruktor yang diberlakukan pada Anda dan menahan diri ke metode biasa - yang dapat diganti dan tidak menerapkan super () setelah Anda membiarkan diri Anda memilih jika, di mana, dan bagaimana Anda ingin memanggil super (sepenuhnya opsional) misalnya:

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

Bersulang!


2
Saya perlu "jelaskan seperti saya berlima" untuk yang satu ini .. Saya merasa ini adalah jawaban yang sangat dalam tetapi rumit dan oleh karena itu diabaikan
swyx

@swyx: keajaiban ada di dalam konstruktor, di mana 'ini' merujuk ke jenis objek yang berbeda bergantung pada jenis objek yang Anda buat. Misalnya, jika Anda sedang membangun DomainObservable baru, this.ObjectConstructor mengacu pada metode yang berbeda yaitu DomainObserveable.ObjectConstructor; sementara jika Anda membuat ArrayObservable baru, this.ObjectConstructor merujuk ke ArrayObservable.ObjectConstructor.
cobberboy

Lihat jawaban saya, saya memposting contoh yang jauh lebih sederhana
Michael Lewis

Saya sepenuhnya setuju @swyx; jawaban ini melakukan terlalu banyak ... saya hanya membacanya dan saya sudah lelah. Saya merasa seperti "jelaskan seperti saya berusia lima tahun DAN benar-benar harus pipis ..."
spb

4

Anda bisa menghilangkan super () di subclass Anda, jika Anda menghilangkan konstruktor sama sekali di subclass Anda. Konstruktor default 'tersembunyi' akan disertakan secara otomatis dalam subkelas Anda. Namun, jika Anda menyertakan konstruktor dalam subkelas Anda, super () harus dipanggil dalam konstruktor itu.

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

Baca ini untuk informasi lebih lanjut.


2

Jawaban dengan justyourimage adalah cara termudah, tetapi contohnya sedikit membengkak. Berikut versi generiknya:

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

Jangan memperpanjang yang asli constructor(), cukup gunakan yang palsu _constructor()untuk logika instantiation.

Catatan, solusi ini membuat proses debug mengganggu karena Anda harus menggunakan metode tambahan untuk setiap pembuatan instance.


Ya, sejauh ini ini adalah metode termudah dan jawaban paling jelas - pertanyaan yang saya ajukan adalah ... "Mengapa, Tuhan, MENGAPA!?" ... Ada alasan yang sangat valid mengapa super () tidak otomatis. .. Anda mungkin ingin meneruskan parameter tertentu ke kelas dasar Anda, sehingga Anda dapat melakukan beberapa pemrosesan / logika / pemikiran sebelum membuat instance kelas dasar.
LFLFM

1

Mencoba:

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


dalam contoh ini Anda juga menggunakan super () dan jika Anda meninggalkannya, Anda akan mendapatkan pengecualian. Jadi saya pikir tidak mungkin untuk menghilangkan panggilan super () ini dalam kasus ini
xhallix

Saya pikir tujuan Anda bukan untuk mengeksekusi konstruktor induk, itulah yang dilakukan kode ini. Anda tidak bisa menghilangkan super saat melakukan perpanjangan.
Jonathan de M.

maaf karena menyesatkan :) Tidak, saya hanya ingin tahu apakah benar-benar perlu menggunakan super () karena saya ingin tahu tentang sintaksnya karena dalam bahasa lain kita tidak perlu memanggil metode super saat memanggil konstruktor untuk kelas turunan
xhallix

1
Menurut developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.jadi saya akan mengatakan menunggu rilis ES6
Jonathan de M.

3
"jadi saya akan bilang tunggu rilis ES6" --- itu sudah dirilis, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms

1

Saya akan merekomendasikan untuk menggunakan OODK-JS jika Anda ingin mengembangkan konsep OOP berikut.

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

Solusi sederhana: Saya rasa sudah jelas tidak perlu penjelasan.

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

Saya tidak yakin mengapa semua kode boilerplate di atas, dan saya tidak yakin apakah ada efek samping untuk pendekatan ini. Ini bekerja untuk saya tanpa masalah.
MyUserInStackOverflow

1
Satu masalah: jika Anda ingin memperluas SubClass Anda lagi, Anda harus membangun fitur skipConstructor ke setiap konstruktor SubClass
Michael Lewis

0

@Bergi disebutkan new.target.prototype, tetapi saya sedang mencari contoh konkret yang membuktikan bahwa Anda dapat mengakses this(atau lebih baik, referensi ke objek yang dibuat dengan kode klien new, lihat di bawah) tanpa harus menelepon super()sama sekali.

Bicara itu murah, tunjukkan kodenya ... Jadi ini contohnya:

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)

Yang akan menghasilkan:

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

Jadi Anda dapat melihat bahwa kita secara efektif membuat objek bertipe B(kelas anak) yang juga merupakan objek bertipe A(kelas induknya) dan di dalam childMethod()anak Bkita telah thismenunjuk ke objek objyang kita buat di B's constructorwith Object.create(new.target.prototype).

Dan semua ini tanpa peduli supersama sekali.

Ini memanfaatkan fakta bahwa di JS a constructordapat mengembalikan objek yang sama sekali berbeda ketika kode klien membuat instance baru dengan new.

Semoga ini bisa membantu seseorang.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.