Dziedziczenie wielokrotne klasy ES6

143

Większość moich badań przeprowadziłem na BabelJS i dalej MDN (który nie ma żadnych informacji), ale możesz mi powiedzieć, czy nie byłem wystarczająco ostrożny, rozglądając się po więcej informacji na temat specyfikacji ES6.

Zastanawiam się, czy ES6 obsługuje dziedziczenie wielokrotne w taki sam sposób, jak inne języki pisane kaczką. Na przykład, czy mogę zrobić coś takiego:

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

rozszerzyć wiele klas do nowej klasy? Jeśli tak, czy interpreter będzie preferował metody / właściwości z ClassTwo zamiast ClassOne?

BTC
źródło
4
To nie jest naprawdę możliwe przy obecnym sposobie dziedziczenia w js, najbliższe co możesz zrobić to mixin
qwertymk
Czy możesz podać jakieś odniesienie, które stwierdza, że ​​nie jest to możliwe w nowej specyfikacji, a jeśli tak, czy możesz uczynić to odpowiedź, abym mógł ją zaakceptować?
BTC
Czytałem, że nowe klasy ES6 nie dodają żadnej nowej funkcjonalności, są po prostu cukrem składniowym.
Oriol
@Oriol, to cukier składniowy, ale zastanawiałem się, czy ten cukier robi coś wewnętrznie z wieloma klasami.
BTC

Odpowiedzi:

72

Obiekt może mieć tylko jeden prototyp. Dziedziczenie z dwóch klas można wykonać, tworząc obiekt nadrzędny jako połączenie dwóch prototypów nadrzędnych.

Składnia podklasy umożliwia to w deklaracji, ponieważ prawa strona extendsklauzuli może być dowolnym wyrażeniem. W ten sposób możesz napisać funkcję, która łączy prototypy według dowolnych kryteriów i wywołać tę funkcję w deklaracji klasy.

Spiczasty
źródło
1
Zawsze się zastanawiałem, czy istnieje sposób na ustawienie metody pobierającej __proto__łącze, aby przekierować wyszukiwanie rekwizytów do właściwego obiektu? Próbowałem, ale nigdy nie udało mi się to zadziałać
qwertymk
3
@qwertymk pamiętaj, że __proto__sam w sobie jest przestarzałą funkcją. Odzwierciedla wewnętrzne łącze prototypu, ale tak naprawdę nie jest to wewnętrzne łącze prototypu.
Pointy
więc nigdy nie ma szans, żeby jakikolwiek taki hack zadziałał? core-js zrobił coś podobnego z obsługą słabych map za pomocą getterów. Wielokrotne dziedziczenie byłoby bardzo fajne
qwertymk
1
@qwertymk cóż, nie mogę powiedzieć z autorytetem, czy jest to zdecydowanie niemożliwe. Osobiście bardzo, bardzo rzadko używam dziedziczenia w JavaScript. W rzeczywistości rzadko używam prototypów, jeśli o to chodzi.
Pointy
2
Oto rozwiązanie, które wymyśliłem: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Próbka: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Metody i właściwości ostatniego konstruktora przekazanego w celu new MultiClassuzyskania najwyższego priorytetu są po prostu dodawane do nowego prototypu. Myślę, że istnieje jeszcze lepsze rozwiązanie, jeśli zostanie ponownie zaimplementowane przy użyciu serwerów proxy ES6, ale nie ma jeszcze wystarczającej obsługi natywnej.
trusktr
97

Sprawdź mój przykład poniżej, supermetoda działa zgodnie z oczekiwaniami. Używanie kilku sztuczek nawet instanceofdziała (w większości przypadków):

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

Wydrukuje

Test D: rozszerza A, B, C -> zewnętrzne wystąpienie D: prawda
from A -> inside instance of A: true
z B -> wewnątrz wystąpienia B: prawda
z C -> wewnątrz wystąpienia C: prawda
z D -> wewnątrz instancji D: prawda
-
Test E: rozszerza A, C -> zewnętrzne wystąpienie E: prawda
from A -> inside instance of A: true
z C -> wewnątrz wystąpienia C: prawda
z E -> wewnątrz instancji E: prawda
-
Test F: rozszerza B -> zewnętrzne wystąpienie F: prawda
z B -> wewnątrz wystąpienia B: prawda
z F -> wewnątrz instancji F: prawda
-
Test G: program do używania samego C z "nowym" dekoratorem, ładny format -> zewnętrzna instancja G: prawda
z C -> wewnątrz wystąpienia C: prawda
-
Sam test B, brzydki format „nowy (B (obiekt))” -> zewnętrzna instancja B: fałsz, ten nie działa
z B -> wewnątrz wystąpienia B: prawda

Link do zabawy

Poelinca Dorin
źródło
1
Można ustalić, że „brzydki formatu” B (Object) poprzez udzielanie B przedłużyć (B||Object).
Aaron
@Aaron, nie jestem do końca pewien, czy śledzę Cię w tym (lub śledzisz mnie). Jeśli F extends (B||Object)zamiast tego F extends B(Object), rozszerzy on mikser B tak, jak jest (jako funkcję), więc F rozszerzy tylko domyślny prototyp funkcji, ponieważ B nigdy nie został wykonany. Używając F extends B(Object), faktycznie wykonujemy funkcję B, a F rozszerzy funkcję „cokolwiek” zwraca funkcja B, w tym przypadku jest to klasa B zdefiniowana wewnątrz funkcji B ... mały hack, aby zachować prawidłowe nazewnictwo klas.
Poelinca Dorin
@Aaron, co moglibyśmy zrobić, to użyć domyślnych parametrów funkcji, const B = (B = Object) => class extends B {a następnie użyć ich class F extends B() {do ładniejszego użycia, ale brzydszy hack Kappa
Poelinca Dorin
const B = (B) => class extends (B||Object) {pozwoli zamienić inst5 = new (B(Object)); // instance only B, ugly formatz inst5 = new (B());, lub być może nie rozumieją kontekst ...
Aaron
@Aaron tak, to działałoby dobrze, dopóki console.log('from B -> inside instance of B: ${this instanceof B}');czarownica nie zawiedzie jako Right-hand side of 'instanceof' is not an object. Używanie, const B = (B = Object) => class extends B {jak wspomniano wcześniej, przejdzie test instanceof i zapewni ci inst5 = new (B());użycie, jeśli chcesz.
Poelinca Dorin
25

Implementacja Sergio Carneiro i Jona wymaga zdefiniowania funkcji inicjalizującej dla wszystkich klas oprócz jednej. Oto zmodyfikowana wersja funkcji agregującej, która zamiast tego używa domyślnych parametrów w konstruktorach. Dołączone są również moje komentarze.

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

Oto małe demo:

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.

Ta funkcja agregująca preferuje właściwości i metody klasy, które pojawiają się później na liście klas.

Chong Lip Phang
źródło
3
Kiedy próbuję użyć tego z React Component, to nie działa. po prostu do Twojej wiadomości każdemu, kto chciałby tego w tym celu.
r3wt
To nadpisuje zmienne i funkcje o tej samej nazwie.
Vincent Hoch-Drei,
Drobnym problemem jest to, że w twoim przykładzie m instanceof Childzwraca false.
Kaczor Donald
19

Justin Fagnani opisuje bardzo czysty (imho) sposób komponowania wielu klas w jedną, wykorzystując fakt, że w ES2015 klasy można tworzyć za pomocą wyrażeń klas .

Wyrażenia a deklaracje

Zasadniczo, tak jak możesz utworzyć funkcję z wyrażeniem:

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

możesz zrobić to samo z zajęciami:

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

Wyrażenie jest oceniane w czasie wykonywania, kiedy kod jest wykonywany, podczas gdy deklaracja jest wykonywana wcześniej.

Używanie wyrażeń klas do tworzenia połączeń

Możesz użyć tego do stworzenia funkcji, która dynamicznie tworzy klasę tylko wtedy, gdy funkcja jest wywoływana:

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

Fajną rzeczą jest to, że możesz wcześniej zdefiniować całą klasę i zdecydować, którą klasę ma ona rozszerzyć do czasu wywołania funkcji:

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

Jeśli chcesz mieszać wiele klas razem, ponieważ klasy ES6 obsługują tylko pojedyncze dziedziczenie, musisz utworzyć łańcuch klas zawierający wszystkie klasy, które chcesz ze sobą mieszać. Powiedzmy, że chcesz utworzyć klasę C, która rozszerza zarówno A, jak i B, możesz to zrobić:

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

Problem polega na tym, że jest bardzo statyczny. Jeśli później zdecydujesz, że chcesz stworzyć klasę D, która rozszerza B, ale nie A, masz problem.

Ale z pewną sprytną sztuczką wykorzystującą fakt, że klasy mogą być wyrażeniami, możesz rozwiązać ten problem, tworząc A i B nie bezpośrednio jako klasy, ale jako fabryki klas (używając funkcji strzałek dla zwięzłości):

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)

Zwróć uwagę, że dopiero w ostatniej chwili decydujemy, które klasy uwzględnić w hierarchii.

Stijn de Witt
źródło
8

Tak naprawdę nie jest to możliwe przy sposobie działania dziedziczenia prototypowego. Przyjrzyjmy się, jak dziedziczone rekwizyty działają w 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

zobaczmy, co się stanie, gdy uzyskasz dostęp do nieistniejącego elementu:

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

Możesz użyć mixinów, aby uzyskać część tej funkcjonalności, ale nie otrzymasz późnego wiązania:

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

vs

var a = {x: 1}
var o = Object.create(a);
o.x; // 1
a.y = 2;
o.y; // 2
qwertymk
źródło
Zaakceptował odpowiedź @ Pointy, ponieważ mówił o słowie kluczowym extends, które jest tym, wokół którego zostało postawione właściwe pytanie, a nie wzorcami dziedziczenia, ale dziękuję za zainteresowanie!
BTC
2

Wymyśliłem takie rozwiązanie:

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

stosowanie:

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'
            ]
        };
    }
}

Tak długo, jak robisz te sztuczki ze swoimi zwyczajowo zapisanymi klasami, można je połączyć. ale my, gdy tylko zechcesz rozszerzyć jakąś funkcję / klasę napisaną inaczej - nie będziesz miał szansy kontynuować pętli.

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

działa dla mnie w węźle v5.4.1 z flagą --harmony

Maikal
źródło
Myślę, że nie potrzebujesz flagi harmonii dla węzła 4x i nowszych.
Umayr
2

używaj Mixins do wielokrotnego dziedziczenia ES6.

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

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}
No8
źródło
3
czy nie oznacza wielokrotnego dziedziczenia one class inherits from 2 or more unrelated classes? Twój przykład pokazuje, że jedna klasa dziedziczy z 2, ale powiązanych klas. To jest dziedziczenie pojedyncze, a nie wielokrotne.
vlad-ardelean
@ vlad-ardelean W rzeczywistości relacja jest sztuczna, tj. ustanowione dynamicznie przez wywołanie classTwo. Bez prawdziwej koncepcji klasy, JS i tak nie ma żadnego strukturalnego dziedzictwa. Nie potrafię sobie wyobrazić scenariusza JS, w którym miksery zachowują się inaczej niż można by się spodziewać konceptualizując je jako MI z prawdziwego świata OO (poza zdefiniowanym „super” łańcuchem); może ktoś bardziej kompetentny ode mnie może go dostarczyć.
zwiastun
@collapsar Myślę, że masz całkowitą rację. JS ma dziedziczenie prototypowe, co oznacza, że ​​istnieje łańcuch prototypów, w którym każdy prototyp w łańcuchu ma jednego rodzica. Podczas mieszania całej grupy klas w łańcuch prototypów w określonej kolejności jest to w rzeczywistości to samo, co MI w świecie obiektów obiektowych.
Stijn de Witt
2

Ze strony es6-features.org/#ClassInheritanceFromExpressions można napisać funkcję agregującą, która pozwoli na wielokrotne dziedziczenie:

class Rectangle rozszerza agregację (Shape, Coloured, 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
}

Ale to jest już dostępne w bibliotekach, takich jak agregacja .

Sergio Carneiro
źródło
2

Moja odpowiedź wygląda na mniej kodu i działa dla mnie:

    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 => {
        Object.getOwnPropertyNames(base.prototype)
        .filter(prop => prop != 'constructor')
        .forEach(prop => Bases.prototype[prop] = base.prototype[prop])
      })
      return Bases;
    }

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

toddmo
źródło
Pomysł, dlaczego mam dostać (z twoim kodem nic się nie zmieniło, tylko wykonanie) Uncaught TypeError: base.prototype.properties is not a function?
exside
1
@exside, ponieważ nie uwzględniłem funkcji rozszerzającej właściwości w moim przykładzie kodu, dlatego lol. Przepraszam. Jest to krótka funkcja rozszerzająca na obiekcie, która pobiera wszystkie właściwości należące do obiektu przy użyciu standardowych funkcji js. Zmieniłem kod, aby usunąć połączenie.
toddmo
1

Cóż, Object. assign daje możliwość zrobienia czegoś bliskiego, choć trochę bardziej przypomina kompozycję z klasami 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();

Nigdzie nie widziałem tego używanego, ale w rzeczywistości jest całkiem przydatne. Możesz użyć function shark(){}zamiast class, ale użycie class ma zalety.

Uważam, że jedyną różnicą w dziedziczeniu ze extendsłowem kluczowym jest to, że funkcja nie działa tylko wprototype ale także na samym obiekcie.

Tak więc teraz, kiedy nie stworzony ma sposobu, a jedynie jego prototyp ma metodynew Shark()sharkbiteeat

Ced
źródło
To nie zadziała. Prototypowe metody nie zostaną wmieszane, a powiązanie będzie nieprawidłowe.
jonschlinkert
1

Nie ma łatwego sposobu na dziedziczenie wielu klas. Podążam za połączeniem skojarzeń i dziedziczenia, aby osiągnąć tego rodzaju zachowanie.

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

Mam nadzieję, że to jest pomocne.

AnandShanbhag
źródło
1

To rozwiązanie ES6 zadziałało dla mnie:

wielokrotne dziedziczenie.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())

Zyski na konsoli przeglądarki:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab
user2006754
źródło
ES6 to JavaScript!
Bergi,
1

Spędziłem pół tygodnia, próbując samemu to rozgryźć, i napisałem o tym cały artykuł https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS i mam nadzieję, że niektórym z was to pomoże.

Krótko mówiąc, oto jak można zaimplementować MI w 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();

A oto specialize_with () jednolinijkowy:

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

Ponownie spójrz na https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .

latitov
źródło
1

w javascript nie możesz nadać klasie (funkcji konstruktora) 2 różnych obiektów prototypowych, a ponieważ dziedziczenie w javascript działa z prototypem, więc nie możesz użyć więcej niż jednego dziedziczenia dla jednej klasy, ale możesz agregować i łączyć właściwości obiektu Prototype i tę główną właściwość wewnątrz klasy ręcznie z refaktoryzacją tych klas nadrzędnych, a następnie rozszerza tę nową wersję i dołączoną klasę do klasy docelowej ma kod dla twojego pytania:

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

};

saman
źródło
0

użyj rozszerzenia z funkcją niestandardową do obsługi dziedziczenia wielokrotnego z 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)

Jon
źródło
0

Dodam też swoje rozwiązanie - uznałem je za najbardziej przyjazne dla siebie z tego, co przeczytałem w tym wątku.

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

Możesz go wtedy użyć w ten sposób:

class _MyBaseClass {}
const MyBaseClass = aggregate(ExtensionOne, ExtensionTwo)(_MyBaseClass);
Ancinek
źródło
0

Jako dowód słuszności wykonałem następującą funkcję. Pobiera listę klas i komponuje je w nową klasę (ostatni prototyp wygrywa, więc nie ma konfliktów). Tworząc funkcję złożoną, użytkownik może wybrać użycie wszystkich oryginalnych konstruktorów [ sic! ] lub przekazać własne. To było największe wyzwanie tego eksperymentu: wymyślić opis tego, co powinien zrobić konstruktor. Kopiowanie metod do prototypu nie jest problemem, ale jaka jest zamierzona logika nowo skomponowanego obiektu. A może nie powinien być konstruktorem? W Pythonie z tego, co wiem, znajduje dopasowanie konstruktor ale funkcje w JS są bardziej akceptujące, stąd do funkcji można przejść prawie wszystko i od podpisu nie będzie to jasne.

Nie sądzę, że jest zoptymalizowany, ale celem było zbadanie możliwości. instanceofnie będzie zachowywał się zgodnie z oczekiwaniami, co, jak sądzę, jest kłopotliwe, ponieważ programiści zorientowani na klasy lubią używać tego jako narzędzia.

Może JavaScript po prostu tego nie ma.

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

Pierwotnie opublikowane tutaj (gist.github.com).

Świadek
źródło
0

Używałem takiego wzorca do programowania złożonych rzeczy dziedziczenia wielokrotnego:

var mammal = {
    lungCapacity: 200,
    breath() {return 'Breathing with ' + this.lungCapacity + ' capacity.'}
}

var dog = {
    catchTime: 2,
    bark() {return 'woof'},
    playCatch() {return 'Catched the ball in ' + this.catchTime + ' seconds!'}
}

var robot = {
    beep() {return 'Boop'}
}


var robotDogProto = Object.assign({}, robot, dog, {catchTime: 0.1})
var robotDog = Object.create(robotDogProto)


var livingDogProto = Object.assign({}, mammal, dog)
var livingDog = Object.create(livingDogProto)

Ta metoda wykorzystuje bardzo mało kodu i pozwala na takie rzeczy, jak nadpisywanie domyślnych właściwości (tak jak robię to z niestandardowym catchTime w robotDogProto)

nathnolt
źródło
-3

Oto niesamowity / naprawdę kiepski sposób na rozszerzenie wielu klas. Używam kilku funkcji, które Babel umieścił w moim transpilowanym kodzie. Funkcja tworzy nową klasę, która dziedziczy class1, class1 dziedziczy class2 i tak dalej. Ma swoje problemy, ale fajny pomysł.

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) {}
Casey
źródło