Đa thừa kế lớp ES6


133

Tôi đã thực hiện hầu hết nghiên cứu về điều này trên BabelJS và trên MDN (không có thông tin nào cả), nhưng xin vui lòng cho tôi biết nếu tôi không đủ cẩn thận trong việc tìm kiếm thêm thông tin về ES6 Spec.

Tôi tự hỏi liệu ES6 có hỗ trợ nhiều kế thừa theo cùng một cách như các ngôn ngữ gõ vịt khác không. Chẳng hạn, tôi có thể làm gì đó như:

class Example extends ClassOne, ClassTwo {
    constructor() {
    }
}

để mở rộng nhiều lớp trên lớp mới? Nếu vậy, trình thông dịch sẽ thích các phương thức / thuộc tính từ ClassTwo hơn ClassOne?


4
Điều này thực sự không khả thi với cách thức kế thừa hiện tại hoạt động trong js, cách gần nhất bạn có thể làm là một mixin
qwertymk

Bạn có thể cung cấp một số loại tài liệu tham khảo mà nói rằng điều này là không thể trong thông số mới, và nếu vậy, bạn có thể làm cho nó một câu trả lời để tôi có thể chấp nhận nó không?
BTC

Tôi đọc các lớp ES6 mới không thêm bất kỳ chức năng mới nào, chúng chỉ là cú pháp đường.
Oriol


@Oriol, chúng là cú pháp đường, nhưng tôi đã tự hỏi liệu đường đó có làm gì với nhiều lớp bên trong không.
BTC

Câu trả lời:


70

Một đối tượng chỉ có thể có một nguyên mẫu. Kế thừa từ hai lớp có thể được thực hiện bằng cách tạo một đối tượng cha như là sự kết hợp của hai nguyên mẫu cha.

Cú pháp cho phân lớp làm cho nó có thể thực hiện điều đó trong khai báo, vì phía bên phải của extendsmệnh đề có thể là bất kỳ biểu thức nào. Vì vậy, bạn có thể viết một hàm kết hợp các nguyên mẫu theo bất kỳ tiêu chí nào bạn thích và gọi hàm đó trong khai báo lớp.


1
Tôi đã luôn tự hỏi, có cách nào để thiết lập một getter trên __proto__liên kết để chuyển tiếp tra cứu prop đến đúng đối tượng không? Tôi đã thử nhưng chưa bao giờ làm cho nó hoạt động
qwertymk

3
@qwertymk hãy nhớ rằng __proto__bản thân nó là một tính năng không dùng nữa. Nó phản ánh liên kết nguyên mẫu bên trong, nhưng nó không thực sự là liên kết nguyên mẫu bên trong.
Pointy

Vì vậy, không bao giờ bất kỳ cơ hội của bất kỳ hack như vậy bao giờ làm việc? core-js đã làm một cái gì đó tương tự với sự hỗ trợ của sơ đồ yếu bằng cách sử dụng getters. Nhiều kế thừa sẽ rất tuyệt
qwertymk

1
@qwertymk tôi không thể nói với chính quyền cho dù điều đó chắc chắn là không thể. Cá nhân tôi sử dụng tính kế thừa trong JavaScript rất, rất hiếm khi. Trong thực tế, tôi sử dụng nguyên mẫu khá hiếm, cho vấn đề đó.
Mũi nhọn

2
Đây là giải pháp tôi đã đưa ra: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Mẫu : class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Các phương thức và thuộc tính của hàm tạo cuối cùng được thông qua new MultiClasscó độ ưu tiên cao nhất, chúng chỉ được trộn vào nguyên mẫu mới. Tôi nghĩ rằng một giải pháp thậm chí còn tốt hơn nếu được triển khai lại bằng ES6 Proxies, nhưng vẫn chưa có đủ hỗ trợ riêng cho nó.
trusktr

87

Kiểm tra ví dụ của tôi dưới đây, superphương pháp làm việc như mong đợi. Sử dụng một vài thủ thuật thậm chí instanceofhoạt động (hầu hết thời gian):

// base class
class A {  
  foo() {
    console.log(`from A -> inside instance of A: ${this instanceof A}`);
  }
}

// B mixin, will need a wrapper over it to be used
const B = (B) => class extends B {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
    console.log(`from B -> inside instance of B: ${this instanceof B}`);
  }
};

// C mixin, will need a wrapper over it to be used
const C = (C) => class extends C {
  foo() {
    if (super.foo) super.foo(); // mixins don't know who is super, guard against not having the method
    console.log(`from C -> inside instance of C: ${this instanceof C}`);
  }
};

// D class, extends A, B and C, preserving composition and super method
class D extends C(B(A)) {  
  foo() {
    super.foo();
    console.log(`from D -> inside instance of D: ${this instanceof D}`);
  }
}

// E class, extends A and C
class E extends C(A) {
  foo() {
    super.foo();
    console.log(`from E -> inside instance of E: ${this instanceof E}`);
  }
}

// F class, extends B only
class F extends B(Object) {
  foo() {
    super.foo();
    console.log(`from F -> inside instance of F: ${this instanceof F}`);
  }
}

// G class, C wrap to be used with new decorator, pretty format
class G extends C(Object) {}

const inst1 = new D(),
      inst2 = new E(),
      inst3 = new F(),
      inst4 = new G(),
      inst5 = new (B(Object)); // instance only B, ugly format

console.log(`Test D: extends A, B, C -> outside instance of D: ${inst1 instanceof D}`);
inst1.foo();
console.log('-');
console.log(`Test E: extends A, C -> outside instance of E: ${inst2 instanceof E}`);
inst2.foo();
console.log('-');
console.log(`Test F: extends B -> outside instance of F: ${inst3 instanceof F}`);
inst3.foo();
console.log('-');
console.log(`Test G: wraper to use C alone with "new" decorator, pretty format -> outside instance of G: ${inst4 instanceof G}`);
inst4.foo();
console.log('-');
console.log(`Test B alone, ugly format "new (B(Object))" -> outside instance of B: ${inst5 instanceof B}, this one fails`);
inst5.foo();

Sẽ in ra

Kiểm tra D: mở rộng A, B, C -> bên ngoài D: true
từ A -> bên trong thể hiện của A: true
từ B -> bên trong thể hiện của B: true
từ C -> bên trong thể hiện của C: true
từ D -> bên trong thể hiện của D: true
-
Kiểm tra E: mở rộng A, C -> bên ngoài E: true
từ A -> bên trong thể hiện của A: true
từ C -> bên trong thể hiện của C: true
từ E -> bên trong thể hiện của E: true
-
Kiểm tra F: mở rộng B -> bên ngoài F: true
từ B -> bên trong thể hiện của B: true
từ F -> bên trong thể hiện của F: true
-
Kiểm tra G: trình bao bọc để sử dụng C một mình với trình trang trí "mới", định dạng đẹp -> phiên bản bên ngoài của G: true
từ C -> bên trong thể hiện của C: true
-
Kiểm tra B một mình, định dạng xấu "mới (B (Object))" -> bên ngoài B: false, lần này không thành công
từ B -> bên trong thể hiện của B: true

Liên kết đến fiddle xung quanh


1
Bạn có thể khắc phục điều đó "định dạng xấu xí" của B (Object) bằng cách làm cho B mở rộng (B||Object).
Aaron

@Aaron tôi không thực sự chắc chắn rằng tôi đang theo dõi bạn về điều này (hoặc bạn đang theo dõi tôi). Nếu F extends (B||Object)thay vào đó F extends B(Object), nó sẽ mở rộng B mixin như nó (như một hàm) vì vậy F sẽ chỉ mở rộng nguyên mẫu Hàm mặc định vì B không bao giờ được thực thi. Bằng cách sử dụng, F extends B(Object)chúng tôi thực sự đang thực hiện chức năng B và F sẽ mở rộng 'bất cứ chức năng B nào trả về, trong trường hợp này, đó là lớp B được xác định bên trong chức năng B ... hack nhỏ để giữ đúng tên lớp.
Poelinca Dorin

@Aaron những gì chúng ta có thể làm là sử dụng các tham số mặc định của chức năng const B = (B = Object) => class extends B {và sau đó sử dụng class F extends B() {cho mục đích sử dụng đẹp hơn, nhưng hack xấu hơn Kappa
Poelinca Dorin

const B = (B) => class extends (B||Object) {sẽ cho phép bạn thay thế inst5 = new (B(Object)); // instance only B, ugly formatbằng inst5 = new (B());, hoặc có lẽ tôi hiểu sai về bối cảnh ...
Aaron

@Aaron vâng, nó sẽ hoạt động tốt cho đến khi console.log('from B -> inside instance of B: ${this instanceof B}');phù thủy sẽ thất bại như Right-hand side of 'instanceof' is not an object. Sử dụng const B = (B = Object) => class extends B {như đã đề cập trước đây sẽ vượt qua bài kiểm tra thể hiện và cung cấp cho bạn inst5 = new (B());cách sử dụng nếu bạn muốn như vậy.
Poelinca Dorin

23

Việc triển khai của Sergio Carneiro và Jon yêu cầu bạn xác định hàm khởi tạo cho tất cả trừ một lớp. Đây là một phiên bản sửa đổi của hàm tổng hợp, thay vào đó sử dụng các tham số mặc định trong các hàm tạo. Bao gồm cũng là một số ý kiến ​​của tôi.

var aggregation = (baseClass, ...mixins) => {
    class base extends baseClass {
        constructor (...args) {
            super(...args);
            mixins.forEach((mixin) => {
                copyProps(this,(new mixin));
            });
        }
    }
    let copyProps = (target, source) => {  // this function copies all properties and symbols, filtering out some special ones
        Object.getOwnPropertyNames(source)
              .concat(Object.getOwnPropertySymbols(source))
              .forEach((prop) => {
                 if (!prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                    Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
               })
    }
    mixins.forEach((mixin) => { // outside contructor() to allow aggregation(A,B,C).staticFunction() to be called etc.
        copyProps(base.prototype, mixin.prototype);
        copyProps(base, mixin);
    });
    return base;
}

Đây là một bản demo nhỏ:

class Person{
   constructor(n){
      this.name=n;
   }
}
class Male{
   constructor(s='male'){
      this.sex=s;
   }
}
class Child{
   constructor(a=12){
      this.age=a;
   }
   tellAge(){console.log(this.name+' is '+this.age+' years old.');}
}
class Boy extends aggregation(Person,Male,Child){}
var m = new Boy('Mike');
m.tellAge(); // Mike is 12 years old.

Hàm tổng hợp này sẽ thích các thuộc tính và phương thức của một lớp xuất hiện sau trong danh sách lớp.


3
Khi tôi thử sử dụng cái này với phản ứng Component, nó không hoạt động. chỉ FYI cho bất cứ ai khác có thể muốn nó cho mục đích này.
r3wt

Điều đó ghi đè lên các biến và hàm có cùng tên.
Vincent Hoch-Drei

17

Justin Fagnani mô tả một cách rất rõ ràng (imho) để kết hợp nhiều lớp thành một bằng cách sử dụng thực tế là trong ES2015, các lớp có thể được tạo bằng các biểu thức lớp .

Biểu thức so với khai báo

Về cơ bản, giống như bạn có thể tạo một hàm với một biểu thức:

function myFunction() {}      // function declaration
var myFunction = function(){} // function expression

bạn có thể làm tương tự với các lớp:

class MyClass {}             // class declaration
var MyClass = class {}       // class expression

Biểu thức được ước tính trong thời gian chạy, khi mã thực thi, trong khi khai báo được thực thi trước.

Sử dụng biểu thức lớp để tạo mixins

Bạn có thể sử dụng điều này để tạo một hàm chỉ tạo một lớp động khi hàm được gọi:

function createClassExtending(superclass) {
  return class AwesomeClass extends superclass {
    // you class body here as usual
  }
}

Điều thú vị ở đây là bạn có thể định nghĩa toàn bộ lớp trước và chỉ quyết định lớp nào sẽ kéo dài theo thời gian bạn gọi hàm:

class A {}
class B {}
var ExtendingA = createClassExtending(A)
var ExtendingB = createClassExtending(B)

Nếu bạn muốn trộn nhiều lớp với nhau, vì các lớp ES6 chỉ hỗ trợ kế thừa duy nhất, bạn cần tạo một chuỗi các lớp có chứa tất cả các lớp bạn muốn trộn với nhau. Vì vậy, giả sử bạn muốn tạo một lớp C mở rộng cả A và B, bạn có thể làm điều này:

class A {}
class B extends A {}
class C extends B {}  // C extends both A and B

Vấn đề với điều này là nó rất tĩnh. Nếu sau này bạn quyết định muốn tạo một lớp D mở rộng B nhưng không phải A, bạn có vấn đề.

Nhưng với một số mánh khóe thông minh sử dụng thực tế là các lớp có thể là biểu thức, bạn có thể giải quyết điều này bằng cách tạo A và B không trực tiếp dưới dạng các lớp, mà là các nhà máy lớp (sử dụng các hàm mũi tên cho ngắn gọn):

class Base {} // some base class to keep the arrow functions simple
var A = (superclass) => class A extends superclass
var B = (superclass) => class B extends superclass
var C = B(A(Base))
var D = B(Base)

Lưu ý cách chúng ta chỉ quyết định vào thời điểm cuối cùng mà các lớp sẽ bao gồm trong hệ thống phân cấp.


8

Điều này thực sự không khả thi với cách thức kế thừa nguyên mẫu hoạt động. Hãy xem cách các đạo cụ kế thừa hoạt động trong js

var parent = {a: function() { console.log('ay'); }};
var child = Object.create(parent);
child.a() // first look in child instance, nope let's go to it's prototype
          // then look in parent, found! return the method

hãy xem điều gì xảy ra khi bạn truy cập vào một chỗ dựa không tồn tại:

child.b; // first look in child instance, nope let's go to it's prototype
         // then look in parent, nope let's go to it's prototype
         // then look in Object.prototype, nope let's go to it's prototype
         // then look at null, give up and return undefined

Bạn có thể sử dụng mixins để có được một số chức năng đó nhưng bạn sẽ không bị ràng buộc muộn:

var a = {x: '1'};
var b = {y: '2'};
var c = createWithMixin([a, b]);
c.x; // 1
c.y; // 2
b.z = 3;
c.z; // undefined

đấu với

var a = {x: 1}
var o = Object.create(a);
o.x; // 1
a.y = 2;
o.y; // 2

Chấp nhận câu trả lời của @ Pointy vì anh ấy đã nói về từ khóa mở rộng, đó là những gì câu hỏi thực tế được đóng khung xung quanh và không phải là mô hình thừa kế, nhưng cảm ơn bạn đã quan tâm!
BTC

2

Tôi sẽ đưa ra giải pháp sau:

'use strict';

const _         = require( 'lodash' );

module.exports  = function( ParentClass ) {

    if( ! ParentClass ) ParentClass = class {};

    class AbstractClass extends ParentClass {
        /**
         * Constructor
        **/
        constructor( configs, ...args ) {
            if ( new.target === AbstractClass )
                throw new TypeError( "Cannot construct Abstract instances directly" );

            super( args );

            if( this.defaults === undefined )
                throw new TypeError( new.target.name + " must contain 'defaults' getter" );

            this.configs = configs;
        }
        /**
         * Getters / Setters
        **/
        // Getting module configs
        get configs() {
            return this._configs;
        }
        // Setting module configs
        set configs( configs ) {
            if( ! this._configs ) this._configs = _.defaultsDeep( configs, this.defaults );
        }
    }

    return AbstractClass;
}

sử dụng:

const EventEmitter  = require( 'events' );
const AbstractClass = require( './abstracts/class' )( EventEmitter );

class MyClass extends AbstractClass {
    get defaults() {
        return {
            works: true,
            minuses: [
                'u can have only 1 class as parent wich was\'t made by u',
                'every othere classes should be your\'s'
            ]
        };
    }
}

Miễn là bạn thực hiện những mánh khóe này với các lớp viết tùy chỉnh của bạn, nó có thể bị xiềng xích. nhưng chúng tôi sẽ sớm mở rộng một số hàm / lớp không được viết như thế - bạn sẽ không có cơ hội tiếp tục vòng lặp.

const EventEmitter  = require( 'events' );
const A = require( './abstracts/a' )(EventEmitter);
const B = require( './abstracts/b' )(A);
const C = require( './abstracts/b' )(B);

hoạt động với tôi trong nút v5.4.1 với cờ --harmony


Tôi không nghĩ bạn cần cờ hài hòa cho nút 4x trở lên.
Umayr

2

Từ trang es6-features.org/#ClassInherributionFromExpressions , có thể viết một hàm tổng hợp để cho phép nhiều kế thừa:

lớp Hình chữ nhật mở rộng tập hợp (Hình dạng, Màu, ZCoord) {}

var aggregation = (baseClass, ...mixins) => {
    let base = class _Combined extends baseClass {
        constructor (...args) {
            super(...args)
            mixins.forEach((mixin) => {
                mixin.prototype.initializer.call(this)
            })
        }
    }
    let copyProps = (target, source) => {
        Object.getOwnPropertyNames(source)
            .concat(Object.getOwnPropertySymbols(source))
            .forEach((prop) => {
            if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                return
            Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
        })
    }
    mixins.forEach((mixin) => {
        copyProps(base.prototype, mixin.prototype)
        copyProps(base, mixin)
    })
    return base
}

Nhưng điều đó đã được cung cấp trong các thư viện như tập hợp .


1

sử dụng Mixins cho ES6 nhiều Kế thừa.

let classTwo = Base => class extends Base{
    // ClassTwo Code
};

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}

3
không phải là thừa kế nhiều nghĩa là one class inherits from 2 or more unrelated classesgì? Những gì ví dụ của bạn cho thấy là một lớp kế thừa từ 2, nhưng các lớp liên quan. Đây là thừa kế đơn, không phải thừa kế nhiều.
vlad-ardelean

@ vlad-ardelean Thực ra mối quan hệ là giả tạo, tức là. thiết lập năng động bằng cách gọi classTwo. Thiếu một khái niệm lớp chính hãng, dù sao thì JS cũng không có sự kế thừa cấu trúc . Trực tiếp Tôi không thể hình dung được một kịch bản JS trong đó các mixin hoạt động khác với những gì bạn mong đợi khi khái niệm chúng là MI từ thế giới OO thực sự (ngoài chuỗi 'siêu' được xác định); có lẽ một số người hiểu biết hơn tôi có thể cung cấp một.
sụp đổ

@collapsar Tôi nghĩ bạn hoàn toàn đúng. JS có sự kế thừa nguyên mẫu, nghĩa là có một chuỗi nguyên mẫu trong đó mỗi nguyên mẫu trong chuỗi có một cha mẹ duy nhất. Khi trộn một nhóm các lớp vào chuỗi nguyên mẫu theo thứ tự xác định, nó thực sự giống như MI trong thế giới OO.
Stijn de Witt

1

Vâng Object.assign cung cấp cho bạn khả năng thực hiện một cái gì đó gần gũi hơn một chút giống như sáng tác với các lớp ES6.

class Animal {
    constructor(){ 
     Object.assign(this, new Shark()) 
     Object.assign(this, new Clock()) 
  }
}

class Shark {
  // only what's in constructor will be on the object, ence the weird this.bite = this.bite.
  constructor(){ this.color = "black"; this.bite = this.bite }
  bite(){ console.log("bite") }
  eat(){ console.log('eat') }
}

class Clock{
  constructor(){ this.tick = this.tick; }
  tick(){ console.log("tick"); }
}

let animal = new Animal();
animal.bite();
console.log(animal.color);
animal.tick();

Tôi chưa thấy cái này được sử dụng ở bất cứ đâu nhưng nó thực sự khá hữu ích. Bạn có thể dùngfunction shark(){} thay vì lớp nhưng có những lợi thế của việc sử dụng lớp thay thế.

Tôi tin rằng điều khác biệt duy nhất với tính kế thừa với extendtừ khóa là hàm không chỉ tồn tại trênprototype mà còn là chính đối tượng.

Vì vậy, bây giờ khi bạn thực hiện new Shark()việc sharktạo có một bitephương thức, trong khi chỉ có nguyên mẫu của nó có một eatphương thức


Điều này sẽ không làm việc. Phương pháp nguyên mẫu sẽ không được trộn lẫn và ràng buộc sẽ sai.
jonschlinkert

1

Không có cách dễ dàng để làm kế thừa nhiều lớp. Tôi theo sự kết hợp giữa liên kết và thừa kế để đạt được loại hành vi này.

    class Person {
        constructor(firstname, lastname, age){
            this.firstname = firstname,
            this.lastname = lastname
            this.Age = age
        }

        fullname(){
                return this.firstname +" " + this.lastname;
            } 
    }

    class Organization {
        constructor(orgname){
            this.orgname = orgname;
        }
    }

    class Employee extends Person{
        constructor(firstname, lastname, age,id) {
            super(firstname, lastname, age);
            this.id = id;
        }

    }
    var emp = new Employee("John", "Doe", 33,12345);
    Object.assign(emp, new Organization("Innovate"));
    console.log(emp.id);
    console.log(emp.orgname);
    console.log(emp.fullname());

Hy vọng điều này là hữu ích.


1

Giải pháp ES6 này hiệu quả với tôi:

đa thừa kế.js

export function allOf(BaseClass, ...Mixins) {

  function copyProperties(target, source) {
    const allPropertyNames = Object.getOwnPropertyNames(source).concat(Object.getOwnPropertySymbols(source))

    allPropertyNames.forEach((propertyName) => {
      if (propertyName.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
        return
      Object.defineProperty(target, propertyName, Object.getOwnPropertyDescriptor(source, propertyName))
    })
  }

  class Base extends BaseClass
  {
    constructor (...args) {
      super(...args)

      Mixins.forEach((Mixin) => {
        copyProperties(this, new Mixin(...args))
      })
    }
  }

  Mixins.forEach((mixin) => {
    copyProperties(Base.prototype, Mixin.prototype)
  })

  return Base
}

main.js

import { allOf } from "./multiple-inheritance.js"

class A
{
    constructor(name) {
        this.name = name
    }
    sayA() {
        return this.name
    }
}

class B
{
    constructor(name) {
        this.name = name
    }
    sayB() {
        return this.name
    }
}

class AB extends allOf(A, B)
{
    sayAB() {
        return this.name
    }
}

const ab = new AB("ab")
console.log("ab.sayA() = "+ab.sayA()+", ab.sayB() = "+ab.sayB()+", ab.sayAB() = "+ab.sayAB())

Sản lượng trên bảng điều khiển trình duyệt:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab

ES6 JavaScript!
Bergi

1

Tôi đã dành nửa tuần để tự mình tìm ra điều này và viết toàn bộ bài viết về nó, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS và hy vọng nó sẽ giúp ích cho bạn.

Nói tóm lại, đây là cách MI có thể được triển khai trong JavaScript:

    class Car {
        constructor(brand) {
            this.carname = brand;
        }
        show() {
            return 'I have a ' + this.carname;
        }
    }

    class Asset {
        constructor(price) {
            this.price = price;
        }
        show() {
            return 'its estimated price is ' + this.price;
        }
    }

    class Model_i1 {        // extends Car and Asset (just a comment for ourselves)
        //
        constructor(brand, price, usefulness) {
            specialize_with(this, new Car(brand));
            specialize_with(this, new Asset(price));
            this.usefulness = usefulness;
        }
        show() {
            return Car.prototype.show.call(this) + ", " + Asset.prototype.show.call(this) + ", Model_i1";
        }
    }

    mycar = new Model_i1("Ford Mustang", "$100K", 16);
    document.getElementById("demo").innerHTML = mycar.show();

Và đây là Special_with () one-liner:

function specialize_with(o, S) { for (var prop in S) { o[prop] = S[prop]; } }

Một lần nữa, vui lòng xem https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .


1

trong javascript, bạn không thể cung cấp cho một lớp (hàm tạo) 2 đối tượng nguyên mẫu khác nhau và vì tính kế thừa trong javascript hoạt động với nguyên mẫu nên bạn không thể sử dụng nhiều hơn 1 thừa kế cho một lớp nhưng bạn có thể tổng hợp và tham gia thuộc tính của đối tượng Prototype và thuộc tính chính đó bên trong một lớp theo cách thủ công với việc tái cấu trúc các lớp cha đó và tiếp theo mở rộng phiên bản mới đó và lớp đã tham gia vào lớp mục tiêu của bạn có mã cho câu hỏi của bạn:

let Join = (...classList) => {

    class AggregatorClass {

        constructor() {
            classList.forEach((classItem, index) => {

                let propNames = Object.getOwnPropertyNames(classItem.prototype);

                propNames.forEach(name => {
                    if (name !== 'constructor') {
                        AggregatorClass.prototype[name] = classItem.prototype[name];
                    }
                });
            });

            classList.forEach(constructor => {
                Object.assign(AggregatorClass.prototype, new constructor())
            });
        }
    }


    return AggregatorClass

};

1

Câu trả lời của tôi có vẻ như ít mã hơn và nó hoạt động với tôi:

class Nose {
  constructor() {
    this.booger = 'ready'; 
  }

  pick() {
    console.log('pick your nose')
  } 
}

class Ear {
  constructor() {
    this.wax = 'ready'; 
  }

  dig() {
    console.log('dig in your ear')
  } 
}

class Gross extends Classes([Nose,Ear]) {
  constructor() {
    super();
    this.gross = true;
  }
}

function Classes(bases) {
  class Bases {
    constructor() {
      bases.forEach(base => Object.assign(this, new base()));
    }
  }
  bases.forEach(base => {
    base.prototype
    .properties()
    .filter(prop => prop != 'constructor')
    .forEach(prop => Bases.prototype[prop] = base.prototype[prop])
  })
  return Bases;
}


// test it
function dontLook() {
  var grossMan = new Gross();
  grossMan.pick(); // eww
  grossMan.dig();  // yuck!
}

0

sử dụng phạm vi với chức năng tùy chỉnh để xử lý nhiều kế thừa với es6

var aggregation = (baseClass, ...mixins) => {
    let base = class _Combined extends baseClass {
        constructor (...args) {
            super(...args)
            mixins.forEach((mixin) => {
                mixin.prototype.initializer.call(this)
            })
        }
    }
    let copyProps = (target, source) => {
        Object.getOwnPropertyNames(source)
            .concat(Object.getOwnPropertySymbols(source))
            .forEach((prop) => {
            if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/))
                return
            Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
        })
    }
    mixins.forEach((mixin) => {
        copyProps(base.prototype, mixin.prototype)
        copyProps(base, mixin)
    })
    return base
}

class Colored {
    initializer ()     { this._color = "white" }
    get color ()       { return this._color }
    set color (v)      { this._color = v }
}

class ZCoord {
    initializer ()     { this._z = 0 }
    get z ()           { return this._z }
    set z (v)          { this._z = v }
}

class Shape {
    constructor (x, y) { this._x = x; this._y = y }
    get x ()           { return this._x }
    set x (v)          { this._x = v }
    get y ()           { return this._y }
    set y (v)          { this._y = v }
}

class Rectangle extends aggregation(Shape, Colored, ZCoord) {}

var rect = new Rectangle(7, 42)
rect.z     = 1000
rect.color = "red"
console.log(rect.x, rect.y, rect.z, rect.color)


0

Tôi cũng sẽ thêm giải pháp của mình - tôi thấy nó thân thiện nhất với bản thân mình từ những gì tôi đọc trong chủ đề này.

export const aggregate = (...mixins) => (Base) => {
  const copyProps = (target, source) => {
    Object.getOwnPropertyNames(source)
      .concat(Object.getOwnPropertySymbols(source))
      .forEach((prop) => {
        if (prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)) {
          return;
        }
        Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop));
      });
  };
  mixins.forEach((mixin) => {
    copyProps(Base, mixin);
    copyProps(Base.prototype, mixin.prototype);
  });
  return Base;
};

Bạn có thể sử dụng nó như thế này:

class _MyBaseClass {}
const MyBaseClass = aggregate(ExtensionOne, ExtensionTwo)(_MyBaseClass);

0

Để chứng minh khái niệm, tôi đã thực hiện chức năng sau. Nó lấy một danh sách các lớp và kết hợp chúng thành một lớp mới (nguyên mẫu cuối cùng chiến thắng để không có xung đột). Khi tạo một hàm tổng hợp, người dùng có thể chọn sử dụng tất cả các hàm tạo ban đầu [ sic! ] hoặc vượt qua của riêng họ. Đây là thách thức lớn nhất của thí nghiệm này: đưa ra một mô tả về những gì nhà xây dựng nên làm. Sao chép các phương thức vào một nguyên mẫu không phải là một vấn đề mà là logic dự định của đối tượng mới được sáng tác. Hoặc có lẽ nó nên được xây dựng? Trong Python, từ những gì tôi biết, nó tìm thấy hàm tạo phù hợp nhưng các hàm trong JS được chấp nhận nhiều hơn, do đó người ta có thể chuyển đến một hàm chỉ về mọi thứ và từ chữ ký sẽ không rõ ràng.

Tôi không nghĩ rằng nó được tối ưu hóa nhưng mục đích là khám phá các khả năng. instanceofTôi đoán sẽ không hành xử như mong đợi, theo tôi, là một người lập dị, vì các nhà phát triển định hướng lớp muốn sử dụng điều này như một công cụ.

Có lẽ JavaScript không có nó.

/*
    (c) Jon Krazov 2019

    Below is an experiment searching boundaries of JavaScript.
    It allows to compute one class out of many classes.

    Usage 1: Without own constructor

    If no constructor is passed then constructor of each class will be called
    with params passed in object. In case of missing params, constructor
    will be called without params.

    Example:

    const MyClass1 = computeClass([Class1, Class2, Class3]);
    const myClass1Instance = new MyClass1({
        'Class1': [1, 2],
        'Class2': ['test'],
        'Class3': [(value) => value],
    });

    Usage 2: With own constructor

    If constructor is passed in options object (second param) then it will
    be called in place of constructors of all classes.

    Example:

    const MyClass2 = computeClass([Class1, Class2, Class3], {
        ownConstructor(param1) {
            this.name = param1;
        }
    });
    const myClass2Instance = new MyClass2('Geoffrey');
*/

// actual function

var computeClass = (classes = [], { ownConstructor = null } = {}) => {
    const noConstructor = (value) => value != 'constructor';

    const ComputedClass = ownConstructor === null
        ? class ComputedClass {
            constructor(args) {
                classes.forEach((Current) => {
                    const params = args[Current.name];

                    if (params) {
                        Object.assign(this, new Current(...params));
                    } else {
                        Object.assign(this, new Current());
                    }
                })
            }
        }
        : class ComputedClass {
            constructor(...args) {
                if (typeof ownConstructor != 'function') {
                    throw Error('ownConstructor has to be a function!');
                }
                ownConstructor.call(this, ...args);
            } 
        };

    const prototype = classes.reduce(
        (composedPrototype, currentClass) => {
            const partialPrototype = Object.getOwnPropertyNames(currentClass.prototype)
                .reduce(
                    (result, propName) =>
                        noConstructor(propName)
                            ? Object.assign(
                                    result,
                                    { [propName]: currentClass.prototype[propName] }
                                )
                            : result,
                    {}
                );

            return Object.assign(composedPrototype, partialPrototype);
        },
        {}
    );

    Object.entries(prototype).forEach(([prop, value]) => {
	Object.defineProperty(ComputedClass.prototype, prop, { value });
    });
    
    return ComputedClass;
}

// demo part

var A = class A {
    constructor(a) {
        this.a = a;
    }
    sayA() { console.log('I am saying A'); }
}

var B = class B {
    constructor(b) {
        this.b = b;
    }
    sayB() { console.log('I am saying B'); }
}

console.log('class A', A);
console.log('class B', B);

var C = computeClass([A, B]);

console.log('Composed class');
console.log('var C = computeClass([A, B]);', C);
console.log('C.prototype', C.prototype);

var c = new C({ A: [2], B: [32] });

console.log('var c = new C({ A: [2], B: [32] })', c);
console.log('c instanceof A', c instanceof A);
console.log('c instanceof B', c instanceof B);

console.log('Now c will say:')
c.sayA();
c.sayB();

console.log('---');

var D = computeClass([A, B], {
    ownConstructor(c) {
        this.c = c;
    }
});

console.log(`var D = computeClass([A, B], {
    ownConstructor(c) {
        this.c = c;
    }
});`);

var d = new D(42);

console.log('var d = new D(42)', d);

console.log('Now d will say:')
d.sayA();
d.sayB();

console.log('---');

var E = computeClass();

console.log('var E = computeClass();', E);

var e = new E();

console.log('var e = new E()', e);

Ban đầu được đăng ở đây (gist.github.com).



-3

Đây là một cách tuyệt vời / thực sự tào lao để mở rộng nhiều lớp. Tôi đang sử dụng một vài chức năng mà Babel đưa vào mã được dịch mã của tôi. Hàm tạo một lớp mới kế thừa class1 và class1 kế thừa class2, v.v. Nó có vấn đề của nó, nhưng một ý tưởng thú vị.

var _typeof = typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol' ? function (obj) {
  return typeof obj
} : function (obj) {
  return obj && typeof Symbol === 'function' && obj.constructor === Symbol ? 'symbol' : typeof obj
}

function _inherits (subClass, superClass) {
  if (typeof superClass !== 'function' && superClass !== null) {
    throw new TypeError('Super expression must either be null or a function, not ' + (
      typeof superClass === 'undefined' ? 'undefined' : _typeof(superClass)))
  }
  subClass.prototype = Object.create(
    superClass && superClass.prototype,
    {
      constructor: {
        value: subClass,
        enumerable: false,
        writable: true,
        configurable: true
      }
    })
  if (superClass) {
    Object.setPrototypeOf
    ? Object.setPrototypeOf(subClass, superClass)
    : subClass.__proto__ = superClass.__proto__  // eslint-disable-line no-proto
  }
}

function _m (...classes) {
  let NewSuperClass = function () {}
  let c1 = NewSuperClass
  for (let c of classes) {
    _inherits(c1, c)
    c1 = c
  }
  return NewSuperClass
}

import React from 'react'

/**
 * Adds `this.log()` to your component.
 * Log message will be prefixed with the name of the component and the time of the message.
 */
export default class LoggingComponent extends React.Component {
  log (...msgs) {
    if (__DEBUG__) {
      console.log(`[${(new Date()).toLocaleTimeString()}] [${this.constructor.name}]`, ...msgs)
    }
  }
}

export class MyBaseComponent extends _m(LoggingComponent, StupidComponent) {}
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.