จะขยายคลาสโดยไม่ต้องใช้ super ใน ES6 ได้อย่างไร?


104

เป็นไปได้ไหมที่จะขยายคลาสใน ES6 โดยไม่เรียกsuperเมธอดเพื่อเรียกใช้คลาสแม่

แก้ไข: คำถามอาจทำให้เข้าใจผิด มันเป็นมาตรฐานที่เราต้องโทร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

ฉันได้รับ expection ในคลาสที่ได้รับของฉันเมื่อเรียกใช้โดยไม่เรียก super () ฉันแก้ไขคำถามเพื่อให้ชัดเจนยิ่งขึ้น :)
xhallix

คุณกำลังทำงานอยู่ในสภาพแวดล้อมใด
Amit

6
คุณไม่มีทางเลือกถ้าคุณขยายคลาสอื่นผู้สร้างต้องเรียกใช้ super () ก่อน
Jonathan de M.

@JonathandeM. ขอบคุณดังนั้นนี่คือสิ่งที่ควรจะเป็นในอนาคตฉันเดาแล้ว?
xhallix

คำตอบ:


153

กฎสำหรับคลาส 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

9
ขอบคุณสำหรับการแก้ไข - ตอนนี้อยู่ที่นั่นแล้ว คุณสามารถทำได้return Object.create(new.target.prototype, …)เพื่อหลีกเลี่ยงการเรียกตัวสร้างขั้นสูง
Bergi


1
คุณควรเพิ่มสิ่งที่เกิดขึ้นถ้าสร้างจะถูกละเว้น: การสร้างเริ่มต้นจะใช้ตามMDN
kleinfreund

12

มีการตอบหลายและความคิดเห็นที่ระบุว่าsuper ต้องconstructorเป็นภายในบรรทัดแรก นั่นเป็นสิ่งที่ผิด คำตอบ @loganfsmyth มีการอ้างอิงที่จำเป็นของข้อกำหนด แต่มันเดือดลงไปที่:

Inheriting ( extends) constructor ต้องโทร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...)"เปิดคอนโซลนักพัฒนาโครเมี่ยมและคลิกที่ "Run Uncaught ReferenceError: this is not definedโค้ด": แน่นอนว่าคุณสามารถใช้เมธอดในตัวสร้างมาก่อนได้super()แต่ก่อนหน้านี้คุณไม่สามารถใช้เมธอดจากคลาสได้!
marcel

ที่คุณไม่สามารถใช้thisมาก่อนsuper()(รหัสของคุณพิสูจน์ได้) ไม่มีส่วนเกี่ยวข้องกับข้อกำหนดในทันที แต่ด้วยการใช้งานจาวาสคริปต์ ดังนั้นคุณต้องเรียก 'super' ก่อน 'this'
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);
}

แก้ไข: พบสิ่งนี้เมื่อdeveloper.mozilla.org:

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

ที่มา


ดังนั้นถ้าฉันเข้าใจคุณถูกต้องฉันจะไม่สามารถใช้วิธีการใด ๆ จากคลาสตัวละครในคลาสฮีโร่ของฉันเมื่อไม่เรียกใช้ super ()? แต่ดูเหมือนจะไม่ถูกต้องทั้งหมดเพราะฉันสามารถเรียกเมธอดจากคลาสพื้นฐานได้ ดังนั้นฉันเดาว่าฉันต้องการซุปเปอร์เมื่อเรียกตัวสร้าง
xhallix

1. ใน OP thisไม่ได้ใช้เลย 2. JS ไม่ใช่แซนวิชและใน ES5 คุณสามารถใช้ได้ตลอดเวลาthisแม้กระทั่งก่อนที่จะเรียกใช้ฟังก์ชันอื่น ๆ ที่คุณต้องการ (ซึ่งอาจกำหนดคุณสมบัติเสริมหรือไม่ก็ได้)
Amit

@amit 1. แล้วตอนนี้ก็ใช้ไม่ได้thisด้วยเหรอ! 2. ระดับของฉัน JS หมายถึงแซนวิชและใน ES6 thisคุณไม่เคยใช้ ฉันแค่พยายามอธิบายคลาส es6 (ด้วยอุปมา) และไม่มีใครต้องการความคิดเห็นที่ทำลายล้าง / ไม่จำเป็นเช่นนั้น
marcel

@marcel ขอโทษสำหรับการดูถูกเหยียดหยาม แต่: 1. เป็นเรื่องเกี่ยวกับการแนะนำปัญหาใหม่ที่ไม่มีอยู่ใน OP 2 เป็นการชี้ให้คุณทราบว่าการเรียกร้องของคุณผิด (ซึ่งยังคงเป็นเช่นนั้น)
Amit

@marcel - ดูคำตอบของฉัน
Amit

4

เพิ่งลงทะเบียนเพื่อโพสต์วิธีแก้ปัญหานี้เนื่องจากคำตอบที่นี่ไม่ตรงใจฉันเลยแม้แต่น้อยเนื่องจากมีวิธีง่ายๆในการแก้ไขปัญหานี้ ปรับรูปแบบการสร้างคลาสของคุณเพื่อเขียนทับตรรกะของคุณในเมธอดย่อยในขณะที่ใช้ตัวสร้างขั้นสูงเท่านั้นและส่งต่ออาร์กิวเมนต์ตัวสร้างไปยังมัน

ในขณะที่คุณไม่ได้สร้างตัวสร้างในคลาสย่อยของคุณต่อ se แต่อ้างอิงเฉพาะเมธอดที่ถูกแทนที่ในคลาสย่อยที่เกี่ยวข้อง

นั่นหมายความว่าคุณตั้งค่าตัวเองให้เป็นอิสระจากฟังก์ชันตัวสร้างที่บังคับใช้กับคุณและละเว้นวิธีการปกติซึ่งสามารถแทนที่ได้และไม่บังคับใช้ 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

@swyx: เวทมนตร์อยู่ในตัวสร้างโดยที่ 'สิ่งนี้' หมายถึงวัตถุประเภทต่างๆขึ้นอยู่กับประเภทของวัตถุที่คุณกำลังสร้าง เช่นถ้าคุณกำลังสร้าง DomainObservable ใหม่ this.ObjectConstructor หมายถึงวิธีการอื่นเช่น DomainObserveable.ObjectConstructor; ในขณะที่คุณกำลังสร้าง ArrayObservable ใหม่ this.ObjectConstructor อ้างถึง ArrayObservable.ObjectConstructor
cobberboy

ดูคำตอบของฉันฉันโพสต์ตัวอย่างที่ง่ายกว่ามาก
Michael Lewis

ฉันเห็นด้วยอย่างสมบูรณ์ @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 () และถ้าคุณปล่อยไว้คุณจะมีข้อยกเว้นเกิดขึ้น ดังนั้นฉันคิดว่ามันเป็นไปไม่ได้ที่จะละเว้นการเรียก super () นี้ในกรณีนี้
xhallix

ฉันคิดว่าเป้าหมายของคุณไม่ใช่การเรียกใช้ตัวสร้างหลักนั่นคือสิ่งที่รหัสนี้ทำ คุณไม่สามารถกำจัด super ได้เมื่อขยาย
Jonathan de M.

ขอโทษที่ทำให้เข้าใจผิด :) ไม่ฉันแค่อยากรู้ว่าจำเป็นต้องใช้ super () จริงๆหรือเปล่าเพราะฉันสงสัยเกี่ยวกับไวยากรณ์เพราะในภาษาอื่นเราไม่ต้องเรียกใช้ super method เมื่อเรียก constructor สำหรับคลาสที่ได้รับ
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);
});

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

1
ปัญหาหนึ่ง: หากคุณต้องการขยายคลาสย่อยของคุณอีกครั้งคุณจะต้องสร้างคุณสมบัติ skipConstructor ในตัวสร้างคลาสย่อยแต่ละตัว
Michael Lewis

1

@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ที่เราสร้างขึ้นในบีด้วยconstructorObject.create(new.target.prototype)

และทั้งหมดนี้โดยไม่ต้องสนใจsuperเลย

นี้ใช้ประโยชน์จากความจริงที่ว่าใน JS สามารถกลับวัตถุที่แตกต่างกันอย่างสมบูรณ์เมื่อรหัสลูกค้าสร้างอินสแตนซ์ใหม่ที่มีconstructornew

หวังว่านี่จะช่วยใครบางคนได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.