Làm thế nào để mở rộng một lớp học mà không cần phải sử dụng super trong ES6?


98

Có thể mở rộng một lớp trong ES6 mà không gọi superphương thức để gọi lớp cha không?

CHỈNH SỬA: Câu hỏi có thể gây hiểu lầm. Nó là tiêu chuẩn mà chúng ta phải gọi super()hay tôi đang thiếu một cái gì đó?

Ví dụ:

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

Khi tôi không gọi super()lớp dẫn xuất, tôi gặp sự cố về phạm vi ->this is not defined

Tôi đang chạy điều này với iojs --harmony trong v2.3.0


Bạn có nghĩa là vấn đề phạm vi là gì? Bạn có nhận được một ngoại lệ (và ở đâu)?
Amit

Tôi nhận được sự khám phá trong lớp dẫn xuất của mình khi gọi nó mà không gọi super (). Tôi thay đổi nội dung câu hỏi của tôi để làm cho nó rõ ràng hơn :)
xhallix

Bạn đang chạy cái này trong môi trường nào?
Amit

6
Bạn không có lựa chọn nào khác nếu bạn mở rộng một lớp khác mà hàm tạo trước tiên phải gọi super ().
Jonathan de M.

@JonathandeM. cảm ơn bạn, vì vậy đây là cách nó được cho là trong tương lai tôi đoán sau đó?
xhallix

Câu trả lời:


147

Các quy tắc cho các lớp ES2015 (ES6) về cơ bản bao gồm:

  1. Trong một phương thức khởi tạo lớp con, thiskhông thể được sử dụng cho đến khi superđược gọi.
  2. Các hàm tạo lớp ES6 PHẢI gọi supernếu chúng là các lớp con hoặc chúng phải trả về một cách rõ ràng một số đối tượng để thay thế đối tượng chưa được khởi tạo.

Điều này liên quan đến hai phần quan trọng của thông số kỹ thuật ES2015.

Phần 8.1.1.3.4 xác định logic để quyết định những gì thistrong hàm. Phần quan trọng đối với các lớp là nó có thisthể ở "uninitialized"trạng thái và khi ở trạng thái này, việc cố gắng sử dụng thissẽ ném ra một ngoại lệ.

Phần 9.2.2 , [[Construct]]xác định hành vi của các hàm được gọi qua newhoặc super. Khi gọi một phương thức thiskhởi tạo lớp cơ sở, được khởi tạo ở bước # 8 [[Construct]], nhưng đối với tất cả các trường hợp khác, thislà chưa được khởi tạo. Vào cuối quá trình xây dựng, GetThisBindingđược gọi, vì vậy nếu supervẫn chưa được gọi (do đó đang khởi tạo this), hoặc một đối tượng thay thế rõ ràng không được trả về, dòng cuối cùng của lệnh gọi hàm tạo sẽ ném ra một ngoại lệ.


1
Bạn có thể đề xuất một cách để kế thừa từ một lớp mà không cần gọi super()không?
Bergi

4
Bạn có nghĩ rằng trong ES6 có thể kế thừa từ một lớp mà không cần gọi super()hàm tạo không?
Bergi

8
Cảm ơn vì bản chỉnh sửa - hiện nó đang ở đó; bạn có thể làm return Object.create(new.target.prototype, …)để tránh gọi hàm tạo siêu.
Bergi


1
Bạn nên thêm điều gì sẽ xảy ra nếu hàm tạo bị bỏ qua: Một hàm tạo mặc định sẽ được sử dụng theo MDN .
kleinfreund

11

Đã có nhiều câu trả lời và nhận xét nói rằng super PHẢI là dòng đầu tiên bên trong constructor. Điều đó chỉ đơn giản là sai. Câu trả lời @loganfsmyth có các tham chiếu bắt buộc của các yêu cầu, nhưng nó trở thành:

Hàm tạo kế thừa ( extends) phải gọi supertrước khi sử dụng thisvà trước khi trả về ngay cả khi thiskhông được sử dụng

Xem phân đoạn bên dưới (hoạt động trong Chrome ...) để biết lý do tại sao có thể có câu lệnh (không sử dụng this) trước khi gọi 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...)"mở các nhà phát triển chrome console và click vào "đoạn mã Run": Uncaught ReferenceError: this is not defined. Chắc chắn, bạn có thể sử dụng các phương thức trong hàm tạo trước đó super()nhưng bạn không thể sử dụng các phương thức từ lớp trước đó!
marcel

mà bạn không thể sử dụng thistrước đây super()(mã của bạn chứng minh điều đó) không liên quan gì đến đặc tả ngay lập tức, nhưng với việc triển khai javascript. Vì vậy, bạn phải gọi 'super' trước 'this'.
marcel

@marcel - Tôi nghĩ rằng chúng tôi đã có rất nhiều nhầm lẫn. Tôi chỉ nói (tất cả cùng) rằng nó là hợp pháp để có STATEMENTS trước khi sử dụng super, và bạn đã nói rằng việc sử dụng thistrước khi gọi là bất hợp pháp super. Chúng tôi cả hai bên phải, chỉ không hiểu nhau :-) (Và đó ngoại lệ là có chủ ý, để hiển thị những gì không phải là quy phạm pháp luật - Tôi thậm chí còn đặt tên cho tài sản 'WillFail')
Amit

9

Cú pháp lớp es6 mới chỉ là một ký hiệu khác cho các "lớp" es5 "cũ" với các nguyên mẫu. Do đó, bạn không thể khởi tạo một lớp cụ thể mà không thiết lập nguyên mẫu của nó (lớp cơ sở).

Đó giống như việc bạn cho phô mai vào bánh sandwich mà không làm. Ngoài ra, bạn không thể cho pho mát trước khi làm bánh sandwich, vì vậy ...

... sử dụng thistừ khóa trước khi gọi siêu lớp với super()cũng không được phép.

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

Nếu bạn không chỉ định một phương thức khởi tạo cho một lớp cơ sở, định nghĩa sau sẽ được sử dụng:

constructor() {}

Đối với các lớp dẫn xuất, hàm tạo mặc định sau được sử dụng:

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

CHỈNH SỬA: Tìm thấy cái này trên developer.mozilla.org:

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

Nguồn


vì vậy nếu tôi hiểu bạn một cách chính xác, tôi sẽ không thể sử dụng bất kỳ phương thức nào từ class Character trên Class Hero của tôi, khi không gọi super ()? Nhưng điều này có vẻ không đúng hoàn toàn, vì tôi có thể gọi các phương thức từ lớp cơ sở. vì vậy tôi đoán tôi chỉ cần siêu khi gọi các nhà xây dựng
xhallix

1. Trong OP, thiskhông được sử dụng ở tất cả. 2. JS không phải là một chiếc bánh sandwich, và trong ES5 bạn luôn có thể sử dụng this, thậm chí trước khi gọi bất kỳ chức năng khác mà bạn thích (sức mạnh đó hoặc có thể không xác định thuộc tính bổ sung)
Amit

@amit 1. Và bây giờ tôi cũng không thể sử dụng this?! 2. Lớp JS của tôi đại diện cho một chiếc bánh sandwich và trong ES6, bạn không phải lúc nào cũng sử dụng được this. Tôi chỉ đang cố gắng giải thích các lớp es6 (bằng một phép ẩn dụ), và không ai cần những nhận xét phá hoại / không cần thiết như vậy.
marcel

@marcel Tôi xin lỗi vì sự giễu cợt, nhưng: 1. là về việc giới thiệu một vấn đề mới không tồn tại trong OP. 2 là điểm chú ý của bạn rằng tuyên bố của bạn là sai (nào vẫn là trường hợp)
Amit

@marcel - Xem câu trả lời của tôi
Amit

4

Chỉ cần đăng ký để đăng giải pháp này vì các câu trả lời ở đây không làm tôi hài lòng ít nhất vì thực sự có một cách đơn giản để giải quyết vấn đề này. Điều chỉnh mẫu tạo lớp của bạn để ghi đè lôgic của bạn trong một phương thức con trong khi chỉ sử dụng hàm tạo siêu và chuyển tiếp các đối số của hàm tạo tới nó.

Như trong bạn không tạo một phương thức khởi tạo trong các lớp con của mình mà chỉ tham chiếu đến một phương thức được ghi đè trong lớp con tương ứng.

Điều đó có nghĩa là bạn tự thoát khỏi chức năng khởi tạo được thực thi theo bạn và kiềm chế với một phương thức thông thường - có thể bị ghi đè và không thực thi super () khi bạn cho phép mình lựa chọn nếu, ở đâu và cách bạn muốn gọi super (đầy đủ tùy chọn) ví dụ:

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

chúc mừng!


2
tôi cần một "giải thích như tôi năm" trên này .. tôi cảm thấy như đây là một câu trả lời rất sâu nhưng phức tạp và có mũi lờ
Swyx

@swyx: điều kỳ diệu nằm bên trong hàm tạo, trong đó 'this' đề cập đến một loại đối tượng khác nhau tùy thuộc vào loại đối tượng bạn đang tạo. Ví dụ: nếu bạn đang xây dựng một DomainObservable mới, this.ObjectConstructor đề cập đến một phương thức khác, tức là DomainObserveable.ObjectConstructor; trong khi nếu bạn đang xây dựng một ArrayObservable mới, this.ObjectConstructor đề cập đến ArrayObservable.ObjectConstructor.
cobberboy

Xem câu trả lời của tôi, tôi đã đăng một ví dụ đơn giản hơn nhiều
Michael Lewis

Tôi hoàn toàn đồng ý @swyx; câu trả lời này đang làm quá nhiều ... Tôi chỉ đọc lướt nó và tôi đã mệt mỏi. Tôi cảm thấy như "giải thích như tôi năm tuổi VÀ thực sự phải đi tiểu ..."
spb

4

Bạn có thể bỏ qua super () trong lớp con của mình, nếu bạn bỏ qua hoàn toàn hàm tạo trong lớp con của mình. Một hàm tạo mặc định 'ẩn' sẽ tự động được đưa vào lớp con của bạn. Tuy nhiên, nếu bạn bao gồm hàm tạo trong lớp con của mình, super () phải được gọi trong hàm tạo đó.

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

Đọc cái này để biết thêm thông tin.


2

Câu trả lời bằng justyourimage là cách dễ nhất, nhưng ví dụ của anh ấy hơi quá đà. Đây là phiên bản chung:

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

Đừng mở rộng cái thật constructor(), chỉ sử dụng cái giả _constructor()cho logic khởi tạo.

Lưu ý, giải pháp này làm cho việc gỡ lỗi trở nên khó chịu vì bạn phải bước vào một phương pháp bổ sung cho mỗi lần khởi tạo.


Vâng, đây là phương pháp dễ dàng nhất và câu trả lời rõ ràng nhất - câu hỏi mà tôi hỏi là ... "Tại sao, Chúa ơi, TẠI SAO !?" ... Có một lý do rất hợp lý tại sao super () không tự động. .. bạn có thể muốn chuyển các tham số cụ thể cho lớp cơ sở của mình, vì vậy bạn có thể thực hiện một số xử lý / logic / suy nghĩ trước khi khởi tạo lớp cơ sở.
LFLFM

1

Thử:

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


trong ví dụ này, bạn cũng sử dụng super () và nếu bạn bỏ nó, bạn sẽ có một ngoại lệ được ném ra. Vì vậy, tôi nghĩ rằng nó không thể bỏ qua cuộc gọi này super () trong trường hợp này
xhallix

Tôi nghĩ mục tiêu của bạn không phải là thực thi hàm tạo cha, đó là những gì mã này làm. Bạn không thể thoát khỏi super khi mở rộng.
Jonathan de M.

xin lỗi vì đã gây hiểu lầm :) Không, tôi chỉ muốn biết liệu có thực sự cần thiết sử dụng super () hay không vì tôi thắc mắc về cú pháp bởi vì trong các ngôn ngữ khác, chúng ta không phải gọi phương thức super khi gọi hàm tạo cho lớp dẫn xuất
xhallix

1
Theo developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… , A constructor *can* use the super keyword to call the constructor of a parent class.vì vậy tôi sẽ nói rằng hãy đợi bản phát hành ES6
Jonathan de M.

3
"vì vậy tôi sẽ nói chờ ES6 phát hành" --- nó đã được phát hành đã có, ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
zerkms

1

Tôi khuyên bạn nên sử dụng OODK-JS nếu bạn có ý định phát triển các khái niệm OOP sau đây.

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

Giải pháp đơn giản: Tôi nghĩ rằng nó rõ ràng không cần giải thích.

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

Tôi không chắc tại sao tất cả mã viết sẵn ở trên và tôi không chắc chắn chính xác liệu có tác dụng phụ đối với cách tiếp cận này hay không. Nó hoạt động cho tôi mà không có vấn đề.
MyUserInStackOverflow

1
Một vấn đề: nếu bạn muốn mở rộng lớp con của bạn một lần nữa, bạn sẽ phải xây dựng các tính năng skipConstructor vào constructor mỗi lớp con
Michael Lewis

0

@Bergi đã đề cập new.target.prototype, nhưng tôi đang tìm kiếm một ví dụ cụ thể chứng minh rằng bạn có thể truy cập this(hoặc tốt hơn, tham chiếu đến đối tượng mà mã máy khách đang tạo new, xem bên dưới) mà không cần phải gọi super().

Nói là rẻ, cho tôi xem mã ... Vì vậy, đây là một ví dụ:

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)

Cái nào sẽ xuất ra:

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

Vì vậy, bạn có thể thấy rằng chúng ta đang tạo hiệu quả một đối tượng kiểu B(lớp trẻ) cũng là một đối tượng kiểu A(lớp cha của nó) và trong childMethod()các con Bchúng tôi đã thistrỏ đến đối tượng objmà chúng tôi tạo ra trong B constructorvới Object.create(new.target.prototype).

Và tất cả những điều này mà không cần quan tâm đến superchút nào.

Điều này thúc đẩy thực tế là trong JS, a constructorcó thể trả về một đối tượng hoàn toàn khác khi mã máy khách tạo một phiên bản mới với new.

Hy vọng điều này sẽ giúp ai đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.