ES6 Class Multiple inheritance
Asked Answered
A

31

216

I've done most of my research on this on BabelJS and on MDN (which has no information at all), but please feel free to tell me if I have not been careful enough in looking around for more information about the ES6 Spec.

I'm wondering whether or not ES6 supports multiple inheritance in the same fashion as other duck-typed languages do. For instance, can I do something like:

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

to extend multiple classes on to the new class? If so, will the interpreter prefer methods/properties from ClassTwo over ClassOne?

Ashy answered 26/4, 2015 at 15:3 Comment(6)
This isn't really possible with the current way inheritance works in js, the closest you can do is a mixinSkippet
Can you provide some kind of reference which states that this is not possible in the new spec, and if so, can you make it an answer so I can accept it?Ashy
I read the new ES6 classes don't add any new functionality, they are just syntax sugar.Phonetic
Here is a pretty good writeup on the mechanics of the class feature.Nihi
@Oriol, they are syntax sugar, but I had wondered if that sugar was doing something with multiple classes internally.Ashy
I think this should be made into a language proposal, this can be helpful.Thumbtack
N
97

An object can only have one prototype. Inheriting from two classes can be done by creating a parent object as a combination of two parent prototypes.

The syntax for subclassing makes it possible to do that in the declaration, since the right-hand side of the extends clause can be any expression. Thus, you can write a function that combines prototypes according to whatever criteria you like, and call that function in the class declaration.

Nihi answered 26/4, 2015 at 15:11 Comment(10)
I was always wondered, is there any way to set a getter on the __proto__ link to forward the prop lookup to the correct object? I've tried but haven't ever gotten it to workSkippet
@Skippet well keep in mind that __proto__ itself is a deprecated feature. It reflects the internal prototype link, but it's not really the internal prototype link.Nihi
so never any chance of any hack like that ever working? core-js did something similar with weakmap support using getters. Multiple inheritance would be very coolSkippet
@Skippet well I can't say with authority whether it's definitely impossible. Personally I use inheritance in JavaScript very, very rarely. In fact I use prototypes pretty rarely, for that matter.Nihi
me too. I just like the technical challenge ;)Skippet
I was actually vaguely hoping that despite the fact that the classes just add some much begotten syntax on top of existing patterns, they would do something to subclass a series of mixins. I've seen the spec use a lot of {Something as Something} syntax, so I thought it may have required something like that, but I did not realize that extends was actually just evaluating an expression as opposed to searching for an actual functional argument or series of functional arguments. Thanks!Ashy
Found a way to write classes who can be extended with parent class. Check my answer )Cyndie
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… lead me to believe that class name [extends] { // class body } means [extends] is an array of classes which would all be mixed into the new class. then again, how would "super()" work?Schwa
Here's the solution I came up with: esdiscuss.org/topic/symbol-for-modifying-property-lookup. Sample: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. The methods and properties of the last constructor passed to new MultiClass have highest precedence, they're just mixed into the new prototype. I think an even better solution exists if re-implemented using ES6 Proxies, but there's not enough native support for it yet.Prato
Any example available?Sirius
S
135

Check my example below, super method working as expected. Using a few tricks even instanceof works (most of the time):

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

Will print out

Test D: extends A, B, C -> outside instance of D: true
from A -> inside instance of A: true
from B -> inside instance of B: true
from C -> inside instance of C: true
from D -> inside instance of D: true
-
Test E: extends A, C -> outside instance of E: true
from A -> inside instance of A: true
from C -> inside instance of C: true
from E -> inside instance of E: true
-
Test F: extends B -> outside instance of F: true
from B -> inside instance of B: true
from F -> inside instance of F: true
-
Test G: wraper to use C alone with "new" decorator, pretty format -> outside instance of G: true
from C -> inside instance of C: true
-
Test B alone, ugly format "new (B(Object))" -> outside instance of B: false, this one fails
from B -> inside instance of B: true

Link to fiddle around

Standoffish answered 10/3, 2016 at 19:3 Comment(10)
You can fix that "ugly format" of B(Object) by making B extend (B||Object).Exoskeleton
@Exoskeleton i'm not really sure i'm following you on this one (or you are following me). If F extends (B||Object) instead of F extends B(Object), it will extend the B mixin as it it (as a function) so F will only extend the default Function prototype since B was never executed. By using F extends B(Object) we are actually executing B function and F will extend 'whatever' B function returns, in this case it's the B class defined inside the B function ... small hack to keep the class naming right.Standoffish
@Exoskeleton what we could do is use function default params const B = (B = Object) => class extends B { and then use class F extends B() { for a prettier usage, but uglier hack KappaStandoffish
const B = (B) => class extends (B||Object) { would let you replace inst5 = new (B(Object)); // instance only B, ugly format with inst5 = new (B());, or perhaps I misunderstand the context...Exoskeleton
@Exoskeleton yes that would work just fine until console.log('from B -> inside instance of B: ${this instanceof B}'); witch will fail as Right-hand side of 'instanceof' is not an object. Using const B = (B = Object) => class extends B { as previously mentioned will pass the instanceof test and provides you with the inst5 = new (B()); usage as well if you wish so.Standoffish
What I wanted to achieve creating those wrappers is to keep the names, extend and instanceof abilities as close as one can get to the 'real deal'. I don't see a point in creating instances out of mixins if we can't attest them afterwards (witch is always a challenge when using mixins).Standoffish
what I am understanding is it is creating multilevel inheritance instead of multiple inheritance, I was finding such example where A extends B and C but there is no relationship between B and CFootwear
This is the solution I went with. I would be very grateful if @PoelincaDorin or anyone else could explain why this works. In particular what is going on in the line const B = (B) => class extends B { ... and also how the 'combining' class definition class D extends C(B(A)) {... actually worksCanella
I downvoted because this is not a true solution to the problem I'm afraid. It will only work under very special conditions. It's only possible if the classes you want to extend have already been "prepared" in that way. If you are the one declaring all of those classes that's great but if some of those classes are being imported from some 3rd party library (e.g. some Js framework you're working with) it is not going to work because those classes will most likely not have been declared in that special way (const my3rdPartyClass = (classToExtend) => ....).Pretend
This is not multiple inheritance, it's just dynamic single inheritance.Cougar
N
97

An object can only have one prototype. Inheriting from two classes can be done by creating a parent object as a combination of two parent prototypes.

The syntax for subclassing makes it possible to do that in the declaration, since the right-hand side of the extends clause can be any expression. Thus, you can write a function that combines prototypes according to whatever criteria you like, and call that function in the class declaration.

Nihi answered 26/4, 2015 at 15:11 Comment(10)
I was always wondered, is there any way to set a getter on the __proto__ link to forward the prop lookup to the correct object? I've tried but haven't ever gotten it to workSkippet
@Skippet well keep in mind that __proto__ itself is a deprecated feature. It reflects the internal prototype link, but it's not really the internal prototype link.Nihi
so never any chance of any hack like that ever working? core-js did something similar with weakmap support using getters. Multiple inheritance would be very coolSkippet
@Skippet well I can't say with authority whether it's definitely impossible. Personally I use inheritance in JavaScript very, very rarely. In fact I use prototypes pretty rarely, for that matter.Nihi
me too. I just like the technical challenge ;)Skippet
I was actually vaguely hoping that despite the fact that the classes just add some much begotten syntax on top of existing patterns, they would do something to subclass a series of mixins. I've seen the spec use a lot of {Something as Something} syntax, so I thought it may have required something like that, but I did not realize that extends was actually just evaluating an expression as opposed to searching for an actual functional argument or series of functional arguments. Thanks!Ashy
Found a way to write classes who can be extended with parent class. Check my answer )Cyndie
developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… lead me to believe that class name [extends] { // class body } means [extends] is an array of classes which would all be mixed into the new class. then again, how would "super()" work?Schwa
Here's the solution I came up with: esdiscuss.org/topic/symbol-for-modifying-property-lookup. Sample: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. The methods and properties of the last constructor passed to new MultiClass have highest precedence, they're just mixed into the new prototype. I think an even better solution exists if re-implemented using ES6 Proxies, but there's not enough native support for it yet.Prato
Any example available?Sirius
S
42

My answer seems like less code and it works for me:

    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 Butt { // left as an exercise for the reader
    
    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();
    console.log(`booger is ${grossMan.booger}!`);
    console.log(`was is ${grossMan.wax}!`);
    grossMan.pick(); // eww!
    grossMan.dig();  // yuck!
Sapling answered 18/5, 2020 at 0:54 Comment(12)
An idea why I would get (with your code, nothing changed, just executing) Uncaught TypeError: base.prototype.properties is not a function?Obryan
@Obryan because I didn't include my properties extension function in my code example, that's why lol. Sorry. It's a short extension function on object that retrieves all the properties that belong to an object using standard js functions. I altered the code to remove the call.Sapling
want a typescript versionYahwistic
plus one for creativity !Coenobite
What an interesting example lmaoPronation
Also works for me, less and simple code, thanks buddyPoe
it is real example of multiple inheritance in jsFootwear
@lynnic, generic type arguments, you want?Sapling
@MuhammadAwais ... it is not. It is an example of single inheritance via a dynamic sub-typing approach (Classes is a factory which dynamically creates a class) where the created class features glue code that does mix-in properties at instance and prototype level (and the latter, mixing into the prototype isn't inheritance either).Bandmaster
@Yahwistic I wanted a TypeScript version too. I wrote one here: https://mcmap.net/q/125559/-es6-class-multiple-inheritance Thanks, @toddmo!Convector
Am I correct to assume that this will break instanceof of? I can't see how instanceof could work on these dynamic extensionsEthnic
Definitely get a vote for the humour ... It ought to count for something :DAuspicious
R
38

Justin Fagnani describes a very clean (imho) way to compose multiple classes into one using the fact that in ES2015, classes can be created with class expressions.

Expressions vs declarations

Basically, just like you can create a function with an expression:

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

you can do the same with classes:

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

The expression is evaluated at runtime, when the code executes, whereas a declaration is executed beforehand.

Using class expressions to create mixins

You can use this to create a function that dynamically creates a class only when the function is called:

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

The cool thing about it is that you can define the whole class beforehand and only decide on which class it should extend by the time you call the function:

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

If you want to mix multiple classes together, because ES6 classes only support single inheritance, you need to create a chain of classes that contains all the classes you want to mix together. So let's say you want to create a class C that extends both A and B, you could do this:

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

The problem with this is that it's very static. If you later decide you want to make a class D that extends B but not A, you have a problem.

But with some smart trickery using the fact that classes can be expressions, you can solve this by creating A and B not directly as classes, but as class factories (using arrow functions for brevity):

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)

Notice how we only decide at the last moment which classes to include in the hierarchy.

I made a library based on these principles that you can have a look at: mics

Recollected answered 2/10, 2017 at 11:31 Comment(2)
What if I have class A built in, and class B built in which extends A, and then make class D extend A. I want all my further classes to extend D, except that I need one class "E" to have B as the original base class, not A? What path of single inheritances can cause E to extend both B and D, and does it matter that it would seem necessary to essentially extend A twice, indirectly, i.e. would this cause issues?Tiertza
@Tiertza I don't think what you propose is possible with single inheritance. A class E cannot simultaneously extend both B and D. You will first have to make a new class BD that is equivalent to D but that extends from B i.s.o. from A. Your new class E can then extend from BD to get an inheritance chain that contains A, B and BD.Recollected
P
34

Sergio Carneiro's and Jon's implementation requires you to define an initializer function for all but one class. Here is a modified version of the aggregation function, which makes use of default parameters in the constructors instead. Included are also some comments by me.

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

Here is a little 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.

This aggregation function will prefer properties and methods of a class that appear later in the class list.

Playlet answered 26/7, 2017 at 16:45 Comment(4)
when i try using this with react Component, it doesn't work. just FYI to anyone else who might have wanted it for this purpose.Parable
That overwrites variables and functions which have the same name.Buoyancy
A minor problem with this is that in your example m instanceof Child returns false.Physiologist
bind|call|apply|toString won't be own property names. Omitting name|length from the prototype will lead to quite a few false positives. And prop.match() won't work when prop is a symbol! Please don't copy blindly.Ferous
S
9

This isn't really possible with the way prototypical inheritance works. Lets take a look at how inherited props work in 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

let's see what happens when you access a prop that doesn't exist:

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

You can use mixins to get some of that functionality but you won't get late binding:

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
Skippet answered 26/4, 2015 at 15:11 Comment(1)
Accepting @Pointy's answer because he talked about the extends keyword which is what the actual question was framed around and not inheritance patterns, but thank you for taking an interest!Ashy
H
3

From the page es6-features.org/#ClassInheritanceFromExpressions, it is possible to write an aggregation function to allow multiple inheritance:

class Rectangle extends aggregation(Shape, Colored, 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
}

But that is already provided in libraries like aggregation.

Hygrometric answered 1/4, 2017 at 14:25 Comment(1)
bind|call|apply|toString won't be own property names. Omitting name|length from the prototype will lead to quite a few false positives. And prop.match() won't work when prop is a symbol!Ferous
C
2

I'v come up with these solution:

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

usage:

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

As long as you'r making these trick with your customly writen classes it can be chained. but us soon as u want to extend some function/class written not like that - you will have no chance to continue loop.

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

works for me in node v5.4.1 with --harmony flag

Cyndie answered 19/1, 2016 at 0:0 Comment(2)
I don't think you need harmony flag for node 4x and above.Zantos
nobody uses nodejs. pleasePhilippopolis
P
2

This ES6 solution worked for me:

multiple-inheritance.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())

Yields on browser-console:

ab.sayA() = ab, ab.sayB() = ab, ab.sayAB() = ab
Predominance answered 10/1, 2018 at 10:38 Comment(1)
ES6 is JavaScript!Ferous
A
2

I spent half a week trying to figure this out myself, and wrote a whole article on it, https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS, and hope it helps some of you.

In short, here's how MI can be implemented in 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();

And here's specialize_with() one-liner:

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

Again, please look at https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS.

Actinomycin answered 22/9, 2019 at 15:9 Comment(0)
L
1

use Mixins for ES6 multiple Inheritence.

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

class Example extends classTwo(ClassOne) {
    constructor() {
    }
}
Latterly answered 29/9, 2016 at 8:33 Comment(3)
isn't multiple inheritance supposed to mean one class inherits from 2 or more unrelated classes? What your example shows is one class inheriting from 2, but related classes. This is single inheritance, not multiple inheritance.Harriet
@Harriet Actually the relation is artificial, ie. established dynamically by calling classTwo. Lacking a genuine class concept, JS has no structural inheritance anyway. Offhand I cannot conceive a JS scenario where mixins behave differently from what you'd expect conceptualizing them as MI from the true OO world (apart from the defined 'super'-chain); maybe some person more knowledgeable than me can supply one.Sigfrid
@Sigfrid I think you are absolutely right. JS has prototypical inheritance, meaning there is a prototype chain where each prototype in the chain has one single parent. When mixing in a whole bunch of classes into the prototype chain in a defined order, it is effectively the same as MI in the OO world.Recollected
B
1

Well Object.assign gives you the possibility to do something close albeit a bit more like composition with ES6 classes.

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

I've not seen this used anywhere but it's actually quite useful. You can use function shark(){} instead of class but there are advantages of using class instead.

I believe the only thing different with inheritance with extend keyword is that the function don't live only on the prototype but also the object itself.

Thus now when you do new Shark() the shark created has a bite method, while only its prototype has a eat method

Bucentaur answered 11/7, 2017 at 3:17 Comment(1)
This won't work. Prototype methods won't be mixed in and the binding will be wrong.Hm
P
1

There is no easy way to do multiple class inheritance. I follow the combination of association and inheritance to achieve this kind of behavior.

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

Hope this is helpful.

Premeditate answered 29/9, 2017 at 11:50 Comment(0)
Y
1

I will add my solution as well - I found it the most friendly for myself from what I read in this thread.

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

You can use it then like this:

class _MyBaseClass {}
const MyBaseClass = aggregate(ExtensionOne, ExtensionTwo)(_MyBaseClass);
Yehudi answered 23/10, 2018 at 19:18 Comment(1)
bind|call|apply|toString won't be own property names. Omitting name|length from the prototype will lead to quite a few false positives. And prop.match() won't work when prop is a symbol! Please don't copy blindly.Ferous
T
1

in javascript you cant give to a class (constructor function) 2 different prototype object and because inheritance in javascript work with prototype soo you cant do use more than 1 inheritance for one class but you can aggregate and join property of Prototype object and that main property inside a class manually with refactoring that parent classes and next extends that new version and joined class to your target class have code for your question :

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

};

Trevino answered 7/2, 2020 at 21:59 Comment(0)
K
1

The solution below (class cloning by copying the instance fields and prototype properties) works for me. I am using normal JS (i.e. not Typescript), with JsDoc annotations and VSCode for compile-time type checking.

The solution interface is very simple:

class DerivedBase extends cloneClass(Derived, Base) {}

//To add another superclass:
//class Der1Der2Base extends cloneClass(Derived2, DerivedBase)

let o = new DerivedBase()  

The classes involved can be created just like normal ES6 classes.

However the following needs to be done:

  • Use isInstanceOf() instead of the builtin operator instanceof.
  • Instead of using 'super' to call non-constructor members of the base class, use the function super2() instead.
  • Don't write any code in the constructors, instead write it in a class method called 'init()' that is in turn called by the constructor. See the example below.
/* Paste the entire following text into the browser's dev console */
/* Tested on latest version of Chrome, Microsoft Edge and FireFox (Win OS)*/
/* Works with JSDoc in VSCode */
/* Not tested on minified/obfuscated code */

//#region library

const log = console.log

/**
 * abbreviation for Object.getPrototypeOf()
 * @param {any} o
 */
function proto(o) { return Object.getPrototypeOf(o) }

/** @param {function} fn */
function callIfNonNull(fn) { if (fn != null) { return fn() } }

/**
 * @param {boolean} b
 * @param {()=>string} [msgFn]
 */
function assert(b, msgFn) {
    if (b) { return }
    throw new Error('assert failed: ' + ((msgFn == null) ? '' : msgFn()))
}

/** @param {any} o */
function asAny(o) { return o }

/**
 * Use this function instead of super.<functionName>
 * @param {any} obj
 * @param {string} attr the name of the function/getter/setter
 * @param {any} cls the class for the current method
 * @param {any[]} args arguments to the function/getter/setter
 */
function super2(obj, attr, cls, ...args) {
    let nms = clsNms(obj)
    assert(nms[0] == nms[1])
    const objCls = proto(obj)
    const superObj = proto(ancestorNamed(objCls, cls.name))
    assert(superObj != obj)
    const attrDscr = getOwnOrBasePropDscr(superObj, attr)
    if (attrDscr == null) { return null }
    let attrVal = attrDscr['value']
    const attrGet = attrDscr['get']
    const attrSet = attrDscr['set']
    if (attrVal == null) {
        if (attrGet != null) {
            if (attrSet != null) {
                assert([0, 1].includes(args.length))
                attrVal = ((args.length == 0) ? attrGet : attrSet)
            } else {
                assert(args.length == 0,
                    () => 'attr=' + attr + ', args=' + args)
                attrVal = attrGet
            }
        } else if (attrSet != null) {
            assert(args.length == 1)
            attrVal = attrSet
        } else {
            assert(false)//no get or set or value!!!!
        }
        assert(typeof attrVal == 'function')
    }
    if (typeof attrVal != 'function') { return attrVal }
    const boundFn = attrVal.bind(obj)
    return boundFn(...args)
}

/**
 * Use this function to call own prop instead of overriden prop
 * @param {any} obj
 * @param {string} attr the name of the function/getter/setter
 * @param {any} cls the class for the current method
 * @param {any[]} args arguments to the function/getter/setter
 */
function ownProp(obj, attr, cls, ...args) {
    let protoObj = ancestorNamed(proto(obj), cls.name)
    const attrDscr = Object.getOwnPropertyDescriptor(protoObj, attr)
    if (attrDscr == null) {
        log(`ownProp(): own property '${attr}' does not exist...`)
        return null
    }
    let attrVal = attrDscr['value']
    const attrGet = attrDscr['get']
    const attrSet = attrDscr['set']
    if (attrVal == null) {
        if (attrGet != null) {
            if (attrSet != null) {
                assert([0, 1].includes(args.length))
                attrVal = ((args.length == 0) ? attrGet : attrSet)
            } else {
                assert(args.length == 0,
                    () => 'attr=' + attr + ', args=' + args)
                attrVal = attrGet
            }
        } else if (attrSet != null) {
            assert(args.length == 1)
            attrVal = attrSet
        } else {
            assert(false)//no get or set or value!!!!
        }
        assert(typeof attrVal == 'function')
    }
    if (typeof attrVal != 'function') {
        log(`ownProp(): own property '${attr}' not a fn...`)
        return attrVal
    }
    const boundFn = attrVal.bind(obj)
    return boundFn(...args)
}

/**
 * @param {any} obj
 * @param {string} nm
 */
function getOwnOrBasePropDscr(obj, nm) {
    let rv = Object.getOwnPropertyDescriptor(obj, nm)
    if (rv != null) { return rv }
    let protObj = proto(obj)
    if (protObj == null) { return null }
    return getOwnOrBasePropDscr(protObj, nm)
}

/**
 * @param {any} obj
 * @param {string} nm
 */
function ancestorNamed(obj, nm) {
    const ancs = ancestors(obj)
    for (const e of ancs) {
        if ((e.name || e.constructor.name) == nm) { return e }
    }
}

/**
 * @template typeOfDerivedCls
 * @template typeOfBaseCls
 * @param {typeOfDerivedCls} derivedCls
 * @param {typeOfBaseCls} baseCls
 * @returns {typeOfDerivedCls & typeOfBaseCls}
 */
function cloneClass(derivedCls, baseCls) {
    const derClsNm = derivedCls['name'], baseClsNm = baseCls['name']
    const gbl = globalThis

    //prevent unwanted cloning and circular inheritance:
    if (isInstanceOf(baseCls, asAny(derivedCls))) { return asAny(baseCls) }
    if (isInstanceOf(derivedCls, asAny(baseCls))) { return asAny(derivedCls) }

    //Object does not derive from anything; it is the other way round:
    if (derClsNm == 'Object') { return cloneClass(baseCls, derivedCls) }

    //use cached cloned classes if available
    if (gbl.clonedClasses == null) { gbl.clonedClasses = {} }
    const k = derClsNm + '_' + baseClsNm, kVal = gbl.clonedClasses[k]
    if (kVal != null) { return kVal }

    //clone the base class of the derived class (cloning occurs only if needed)
    let derBase = cloneClass(proto(derivedCls), baseCls)

    //clone the derived class

    const Clone = class Clone extends derBase {

        /**  @param {any[]} args */
        constructor(...args) {
            super(...args)
            ownProp(this, 'init', Clone, ...args)
        }
    }

    //clone the properties of the derived class
    Object.getOwnPropertyNames(derivedCls['prototype'])
        .filter(prop => prop != 'constructor')
        .forEach(prop => {
            const valToSet =
                Object.getOwnPropertyDescriptor(derivedCls['prototype'], prop)
            if (typeof valToSet == 'undefined') { return }
            Object.defineProperty(Clone.prototype, prop, valToSet)
        })

    //set the name of the cloned class to the same name as its source class:
    Object.defineProperty(Clone, 'name', { value: derClsNm, writable: true })

    //cache the cloned class created
    gbl.clonedClasses[k] = Clone
    log('Created a cloned class with id ' + k + '...')
    return asAny(Clone)
}

/**
 * don't use instanceof throughout your application, use this fn instead
 * @param {any} obj
 * @param {Function} cls
 */
function isInstanceOf(obj, cls) {
    if (obj instanceof cls) { return true }
    return clsNms(obj).includes(cls.name)
}

/** @param {any} obj */
function clsNms(obj) {
    return ancestors(obj).map(/** @param {any} e */ e =>
        e.name || e.constructor.name)
}

/**
 * From: https://gist.github.com/ceving/2fa45caa47858ff7c639147542d71f9f
 * Returns the list of ancestor classes.
 *
 * Example:
 *   ancestors(HTMLElement).map(e => e.name || e.constructor.name)
 *   => ["HTMLElement", "Element", "Node", "EventTarget", "Function", "Object"]
 * @param {any} anyclass
 */
function ancestors(anyclass) {
    if (anyclass == null) { return [] }
    return [anyclass, ...(ancestors(proto(anyclass)))]
}

//#endregion library

//#region testData
class Base extends Object {

    /** @param {any[]} args */
    constructor(...args) {//preferably accept any input
        super(...args)
        ownProp(this, 'init', Base, ...args)
    }

    /** @param {any[]} _args */
    init(..._args) {
        log('Executing init() of class Base...')
        //TODO: add code here to get the args as a named dictionary

        //OR, follow a practice of parameterless constructors and
        //initial-value-getting methods for class field intialization

        /** example data field of the base class */
        this.baseClsFld = 'baseClsFldVal'
    }

    m1() { log('Executed base class method m1') }

    b1() { log('Executed base class method b1') }

    get baseProp() { return 'basePropVal' }
}

class Derived extends Object {//extend Object to allow use of 'super'

    /** @param {any[]} args */
    constructor(...args) {//convention: accept any input
        super(...args)
        ownProp(this, 'init', Derived, ...args)
    }

    /** @param {any[]} _args */
    init(..._args) {
        log('Executing init() of class Derived...')
        this.derClsFld = 'derclsFldVal'
    }

    m1() {
        const log = /** @param {any[]} args */(...args) =>
            console.log('Derived::m1(): ', ...args)
        log(`super['m1']: `, super['m1'])
        super2(this, 'm1', Derived)

        log(`super['baseProp']`, super['baseProp'])

        log(`super2(this, 'baseProp', Derived)`,
            super2(this, 'baseProp', Derived))

        log(`super2(this, 'nonExistentBaseProp', Derived)`,
            super2(this, 'nonExistentBaseProp', Derived))
    }

    m2() {
        log('Executed derived class method 2')
    }
}

class DerivedBase extends cloneClass(Derived, Base) {

    /** @param {any[]} args */
    constructor(...args) {
        super(...args)
        ownProp(this, 'init', DerivedBase, ...args)
    }

    /** @param {any[]} _args */
    init(..._args) {
        log('Executing init() of class DerivedBase...')
    }
}

log('Before defining o (object of DerivedBase)...')
let o = new DerivedBase()
log('After defining o (object of DerivedBase)...')

class Derived2 extends Base {

    /** @param {any} args */
    constructor(...args) {
        //convention/best practice: use passthrough constructors for the classes
        //you write
        super(...args)
        ownProp(this, 'init', Derived2, ...args)
    }

    /**
     * @param {any[]} _args
     */
    init(..._args) {
        log('Executing init() of class Derived2...')
    }

    derived2func() { log('Executed Derived2::derived2func()') }
}

class Der1Der2Base extends cloneClass(Derived2, DerivedBase) {

    /** @param {any} args */
    constructor(...args) {
        //convention/best practice: use passthrough constructors for the classes
        //you write
        super(...args)
        ownProp(this, 'init', Der1Der2Base, ...args)
    }

    /** @param {any[]} _args */
    init(..._args) {
        log('Executing original ctor of class Der1Der2Base...')
    }

}

log('Before defining o2...')
const o2 = new Der1Der2Base()
log('After defining o2...')

class NotInInheritanceChain { }
//#endregion testData

log('Testing fields...')
log('o.derClsFld:', o.derClsFld)
log('o.baseClsFld:', o.baseClsFld)
//o.f3  JSDoc gives error in VSCode

log('Test method calls')
o.b1()
o.m1()
o.m2()
//o.m3() //JSDoc gives error in VSCode
log('Test object o2')
o2.b1()
o2.m1()
o2.derived2func()

//don't use instanceof throughout your application, use this fn instead
log('isInstanceOf(o,DerivedBase)', isInstanceOf(o, DerivedBase))
log('isInstanceOf(o,Derived)', isInstanceOf(o, Derived))
log('isInstanceOf(o,Base)', isInstanceOf(o, Base))

log('isInstanceOf(o,NotInInheritanceChain)',
    isInstanceOf(o, NotInInheritanceChain))

A note of caution: the JSDoc intersection operator & may not always work. In that case some other solution may need to be used. For example, a separate interface class may need to be defined that will 'manually' combine the 2 classes. This interface class can extend from one of the classes and the other class's interface can be automatically implemented using VsCode quick fix option.

Koval answered 23/1, 2021 at 6:38 Comment(0)
G
1

Instead of use inheritance I recomend to use composition it's more flexible and you get the same profit that is reuse same code in different classes.

import Controller from 'your-favorite-framework';

class Example extends ClassTwoMixin(ClassOneMixin(Controller)) {
  constructor() {
  }
}

const ClassOneMixin = (superclass) => class extends superclass {}

const ClassTwoMixin = (superclass) => class extends superclass {}

To learn more about, search: "Composition over inheritance"

Glycolysis answered 29/10, 2021 at 11:16 Comment(4)
what is the superclass passed to ClassOneMixin here ?Hockett
The base class that you want to use, for example if in your framework you need to compose ClassTwoMixin and ClassOneMixin to a component it wold be like: class Example extends ClassTwoMixin(ClassOneMixin(componment))Glycolysis
I'm struggling to understand this... both ClassOneMixin and ClassTwoMixin appear to be defined identically, so why do you need two different names with the same definition? And how does this help you for example with a practical case where you have two base classes you want to extend? I don't see where classes ClassOne and ClassTwo are referenced at all in the above code.Tiertza
It was a usage example, basically every class you make needs to follow the same signature then you compose them at the top level classAcquisitive
T
1

I write to understand and add flexibility to js programming. This code was written with reference to the code above.

class A{
    constructor(name)
    {
        this.name=name;
    }
    getname=function(){return this.name};
    }
    B=(x)=>(class B extends (()=>x||Object)(){
    constructor(surname,name)
    {   super(name);
        this.surname=surname;
    }
    getsurname(){return this.surname};
    })
    class C extends B(A){
    constructor(name,surname)
    {
        super(surname,name);
    }
    getfullname(){return this.name+" "+this.surname};
    };

    let person=new C("Ed","Boon");
    console.log(person.getname());//output Ed
    console.log(person.getsurname());//output Boon
    console.log(person.getfullname());//output Ed Boon
    console.log(person);
  console.log(person.__proto__.constructor); //person.__proto__  properties inherit from C class
   console.log(person.__proto__.__proto__.constructor); //person.__proto__.__proto__  properties inherit from B class
   console.log(person.__proto__.__proto__.__proto__.constructor); //person.__proto__.__proto__ .__proto__  properties inherit from A class
note: person instanceof A true but person instanceof B false, because B seem as function. B only appears as a class, inside run a code defined in class B.

I add second alternative way

   

 //extendsClass function using for create temporary extendedfirst argument baseClass ,other arguments is classes using for inherit
      function extendsClass(...cls)
{
  let str="";

   for (let i=arguments.length-1;i>0;i--)
    {
       str+=(i==1?"(":"")+(arguments[i-1]+"").replace(RegExp(arguments[i-1].name+"({|\\s.*{)?"),arguments[i-1].name+" extends "+arguments[i].name+" {").replaceAll("//super","super")+(i==1?")":"");
       
    }
    return eval(str);
}
        class A{
        constructor(name)
        {
            this.name=name;
        }
        getname=function(){return this.name};
        run(){console.log(`instance of A ${this instanceof A}`)};
        }
        class B {
        constructor(surname,name)
        {   
            //super(name);
            this.surname=surname;
        }
        getsurname(){return this.surname};
        run(){
            //super.run();
        console.log(`instance of B ${this instanceof B}`)};
        }
        class C {
        constructor(name,surname)
        {
          //super(surname,name);
        }

        getfullname(){return this.name+" "+this.surname};
        };
         class D extends extendsClass(C,B,A) {
        constructor(name,surname,address)
        {
          super(name,surname);
          this.address=address;
        }
        }   
//extendsClass function create temprory, class C extends from B,B extends from A, A class dont create temporary stay as global class.
        var person=new (extendsClass(C,B,A))("Ed","Boon");
        console.log(person.getname());//output Ed
        console.log(person.getsurname());//output Boon
        console.log(person.getfullname());//output Ed Boon
        person.run();
        var person2=new D("Cem","Firat","A place in the world");
        console.log(person2.getname());//output Cem
        console.log(person2.getsurname());//output Firat
        console.log(person.getfullname());//output Cem Firat
        person2.run();
Total answered 22/12, 2021 at 7:13 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Overnice
E
1

I created a library that lets you fake multi inheritance in es6. The mini library preprocesses the code using some of the above code. and uses mixins to emulate multiple class inheritance. There is a significant difference between using mixins and native multiple inheritance.

Mixins in JavaScript are a way to reuse code between different classes. A mixin is a class whose methods can be reused in other classes, without there being a formal inheritance relationship. Mixins allow you to share methods between different classes, but they do not imply a hierarchical relationship between the classes.

On the other hand, multiple inheritance is a feature of some object-oriented programming languages in which a class can inherit from more than one superclass. This can lead to a much more complex hierarchy of classes, but it can also offer more flexibility.

It’s important to note that JavaScript does not natively support multiple inheritance. This is a deliberate design choice by the creators of the language to reduce complexity and avoid certain problems that can occur with multiple inheritance (such as the diamond problem).

Therefore, although mixins in JavaScript can fulfill a similar function as multiple inheritance in some other languages, they are not the same and have different implications for the design and structure of your code. Using mixins can be a powerful way to share and reuse code in JavaScript, while avoiding some of the pitfalls of multiple inheritance.

For example now you can do this:

class a{

    methodA() {

        console.log( "Say, Hi" )

    }

}

class b{

    pleaseCallAMethodOfA() {

        this.methodA();

    }

}


class c extends a, b{

}

var instanceC = new c();

instanceC.pleaseCallAMethodOfA();

Or this:

class a{

    constructor( a ) {

        this.otherTest = a;

    }

}

class b{

    constructor( a ) {

        this.test = a;

    }

    testSuper() {

        console.log( this.test + this.otherTest );

    }

}


class c extends a, b{

    constructor() {

        super( 10 );

    }

}

var instance = new c();

instance.testSuper();

You can extend as many classes as you want, Not only 2. You can find the code here:

https://code.unifyjs.org/TheUnifiedCompany/JS-Class-Multiple-inheritance/

Excitable answered 7/4 at 14:30 Comment(4)
1/2 ... Whatever one tries, whichever approach gets chosen, none of it will ever achieve multiple inheritance. JavaScript just does not support it. Period. The closest achievement possible is the single inheritance from one of the classes one wants to inherit from (who decides which of the many classes should be picked?) while for the rest one has to implement mixin functionality/behavior at both, instance level and prototype level, the latter at the prototype of the very class one has chosen to actual inherit from.Bandmaster
2/2 ... Your implementation just does prove it for it just covers the most common/widespread approach of ... class A extends fakeMultiInheritanceHook(B, C, D) { /* */ } ... behind the fake syntax of ... class A extends B, C, D { /* */ } ... but it can not, like all the others, escape the true single/linear inheritance nature of JS.Bandmaster
Yes you are right, I changed my description, Thanks.Excitable
perfect, thanks for the update on / clarification of the matter (+1).Bandmaster
W
0

use extent with custom function to handle multiple inheritance with 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)
Warram answered 27/6, 2017 at 9:37 Comment(1)
bind|call|apply|toString won't be own property names. Omitting name|length from the prototype will lead to quite a few false positives. And prop.match() won't work when prop is a symbol! Please don't copy blindly.Ferous
U
0

As a proof of concept, I did the following function. It takes a list of classes and composes them into a new class (the last prototype wins so there are no conflicts). When creating a composed function, the user can choose to use all original constructors [sic!] or pass their own. This was the biggest challenge of this experiment: to come up with a description of what constructor should do. Copying methods into a prototype is not an issue but what's the intended logic of newly composed object. Or maybe it should be constructorless? In Python, from what I know, it finds the matching constructor but functions in JS are more accepting, hence one can pass to a function just about everything and from signature it won't be clear.

I don't think it's optimised but the purpose was exploring possibilities. instanceof will not behave as expected which, I guess, is a bummer, since class-oriented developers like to use this as a tool.

Maybe JavaScript just doesn't have it.

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

Originally posted here (gist.github.com).

Upsilon answered 5/6, 2019 at 8:4 Comment(0)
R
0

I have been using a pattern like this to program complex multi inheritance things:

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)

This method uses very little code, and allows for things like overwriting default properties (like I do with a custom catchTime in robotDogProto)

Rum answered 4/9, 2020 at 11:40 Comment(0)
S
0

Answer

This is impossible. An object can only have one prototype in Javascript.

Workaround

You could use Object.assign which would work, but is unsafe and doesn't provide autocomplete or any type safety.

class Example {
  constructor (props) {
    Object.assign(this, new Class1(props))
    Object.assign(this, new Class2(props))
  }
}

Another way you should consider

In this example I'm assuming the goal of extending many is to be able to construct the Example class in one line, then have access to the methods in all the would-be-extended classes.

Optional: If the methods depend on methods in the super-class I suggest to create another BaseClass and have class Class1 extends BaseClass {}. This way the re-useable part of Example doesn't get rewritten.

class Class1 extends BaseClass {}
class Class2 extends BaseClass {}

class Example {
  class1: Class1 // Sorry for the Typescript
  class2: Class2

  constructor (props) {
    this.class1 = new Class1(props)
    this.class2 = new Class2(props)
  }
}

const example = new Example(props)
example.class1.someMethod()
Saire answered 15/2, 2021 at 11:14 Comment(0)
N
0

I faced the same problem recently. I created a series of model-like classes and wanted to create a few interface-like classes to extend my models. HasGuid or HasName. Only to realise that JS classes don't support multiple inheritance. I've created the solution below. Default values are also copied. I guess you can also use it to sync 2 objects after they have some values in them.

Only disadvantage is that you have to provide an instance instead of a class name.

export default class JsExtend
{
    static extend(target, owner)
    {
        const METHOD_BLACKLIST = ['constructor'];

        /**
         * Methods
         */
        Object.getOwnPropertyNames(Object.getPrototypeOf(owner)).forEach(key => {
            
            if (METHOD_BLACKLIST.includes(key) == false)
                target[key] = owner[key];
        });

        /**
         * Properties - keys
         */
        Object.keys(owner).forEach(key => {
            target[key] = owner[key];
        });
    }
}

Usage:

export default class VideoModel
{

    constructor()
    {
        JsExtend.extend(this, new HasGuid());
        JsExtend.extend(this, new CanCreate());
    }
}

EDIT: Static properties / methods are copied as normal methods!

Nellynelms answered 1/7, 2022 at 8:3 Comment(0)
A
0

Extending from Multiple has two hacky solutions:

  • Aggregating mixins to a base class
  • Wrapping an extend base class as a mixin function

Aggregating mixins to a base class

There are thousand of implementations out there but this is my implementation:

const aggregator = (base, ...mixins) => {
  const blackList = /^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/
  const copyAttributes = (target, source) => {
    Object.getOwnPropertyNames(source)
      .concat(Object.getOwnPropertySymbols(source))
      .forEach(prop => {
        if (prop.match(blackList)) return
        Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
      })
  }

  return class extends base {
    constructor(...args) {
      super(...args)
      for (const mixin of mixins) {
        const target = this.constructor
        copyAttributes(target.prototype, mixin.prototype)
        copyAttributes(target, mixin)
      }
    }
  }
}

The idea is very simple, it returns and extended class of a base class. Inside this extended class we copy attributes and functions of each mixin.

Here is a working snippet with some tests.

const aggregator = (base, ...mixins) => {
  const blackList = /^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/
  const copyAttributes = (target, source) => {
    Object.getOwnPropertyNames(source)
      .concat(Object.getOwnPropertySymbols(source))
      .forEach(prop => {
        if (prop.match(blackList)) return
        Object.defineProperty(target, prop, Object.getOwnPropertyDescriptor(source, prop))
      })
  }

  return class extends base {
    constructor(...args) {
      super(...args)
      for (const mixin of mixins) {
        const target = this.constructor
        copyAttributes(target.prototype, mixin.prototype)
        copyAttributes(target, mixin)
      }
    }
  }
}


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

class B {
  foo() {
    if (super.foo) super.foo()
    console.log(`from B -> inside instance of B: ${this instanceof B}`)
  }
}

class C {
  foo() {
    if (super.foo) super.foo()
    console.log(`from C -> inside instance of C: ${this instanceof C}`)
  }
}

// D class, extends A and aggregates B and C
const CBA = aggregator(A, B, C)
class D extends CBA {
  foo() {
    if (super.foo) super.foo()
    console.log(`from D -> inside instance of D: ${this instanceof D}`)
  }
}

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

// F class, extends B
class F extends B {
  foo() {
    if (super.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 aggregator(C) {}

// H class, extends B
const H = aggregator(B)

// J class, extends Object and aggregates B
const J = aggregator(Object, B)

const d = new D()
const e = new E()
const f = new F()
const g = new G()
const h = new H()
const j = new J()

console.log(`Test D:
Class: D extends A, and aggregates B and C
Instance of D: ${d instanceof D}`)
d.foo()
console.log('-')

console.log(`Test E:
Class: E extends A, and aggregates C
Instance of E: ${e instanceof E}`)
e.foo()
console.log('-')
console.log(`Test F: 
Class: F extends B
Instance of F: ${f instanceof F}`)
f.foo()
console.log('-')
console.log(`Test G:
Class: G wrapped to use C alone with "new" decorator
Instance of G: ${g instanceof G}`)
g.foo()
console.log('-')
console.log(`Test H:
Class: H extend B,
Instanceof B: ${h instanceof B}`)
h.foo()
console.log('-')
console.log(`Test J:
Class: H extend Object,
Instance of B: ${j instanceof B}, this one fails`)
h.foo()

The problem with this implementation is that is only extending the base class. This case is more obvious in the Test J:

Wrapping an extend base class as a mixin function

A direct solution is using a mixin function wrapper. But everything has its own cost. The cost of this approach is that we need to modify some classes as mixin function wrapper. Let's use the previous snippet as example.

We will convert B and C classes as mixin wrappers like this:

// B mixin
const B = c => class extends c {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from B] is instance of B: ${this instanceof c}`)
  }
}

// C mixin
const C = c => class extends c {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from C] is instance of C: ${this instanceof c}`)
  }
}

Now we extend into a regular class like this:



// D class, extends A class and B and C mixins
class D extends C(B(A)) {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from D] is instance of D: ${this instanceof D}`)
  }
}

Here is a working snippet with some test:

class A {
  foo() {
    console.log(`[from A] is instance of A: ${this instanceof A}`)
  }
}

// B mixin
const B = c => class extends c {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from B] is instance of B: ${this instanceof c}`)
  }
}

// C mixin
const C = c => class extends c {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from C] is instance of C: ${this instanceof c}`)
  }
}

// D class, extends A class and B and C mixins
class D extends C(B(A)) {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from D] is instance of D: ${this instanceof D}`)
  }
}

// E class, extends A class and C mixin
class E extends C(A) {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from E] is instance of E: ${this instanceof E}`)
  }
}

// F class, extends B mixin
class F extends B(Object) {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from F] is instance of F: ${this instanceof F}`)
  }
}

// G class, wraps C mixin. Will use new decorator for instantiation, pretty format
class G extends C(Object) {}

const d = new D()
const e = new E()
const f = new F()
const g = new G()
const h = new (B(Object))

console.log(`Test D:
Class: D extends A, B and C
Instance of D: ${d instanceof D}`)
d.foo()
console.log('-')

console.log(`Test E:
Class: E extends A and C
Instance of E: ${e instanceof E}`)
e.foo()
console.log('-')
console.log(`Test F: 
Class: F extends B
Instance of F: ${f instanceof F}`)
f.foo()
console.log('-')
console.log(`Test G:
Class: G wrapped to use C alone with "new" decorator
Instance of G: ${g instanceof G}`)
g.foo()
console.log('-')

const isInstanceOfB = i => {
  try {
    return i instanceof B
  } catch (e) {
    return false
  }
}
console.log(`Test H:
Class: Anonymous class extends Object 
Instance of B: ${isInstanceOfB(h)}
Instance of Object: ${h instanceof Object}`)
h.foo()

Test D clearly shows that D class is extending A, B and C.

Test D:
Class: D extends A, B and C
Instance of D: true
[from A] is instance of A: true
[from B] is instance of B: true
[from C] is instance of C: true
[from D] is instance of D: true

Also, we can appreciate in Test H H does not extends B because is a mixin that extends

Test H:
Class: Anonymous class does has a prototype 'undefined' 
Instance of B: false
Instance of Object: true
[from B] is instance of B: true

Also we can add a builder for better syntax like this:

const extender = base => new MixinBuilder(base)

// Our helper class that will make things look better
class MixinBuilder {
  constructor(base) {
    this.base = base;
  }
  with(...mixins) {
    return mixins.reduce((c, mixin) => mixin(c), this.base);
  }
}

const extender = base => new MixinBuilder(base)

class MixinBuilder {
  constructor(base) {
    this.base = base
  }
  
  with(...mixins) {
    return mixins.reduce((c, mixin) => mixin(c), this.base)
  }
}

class A {
  foo() {
    console.log(`[from A] is instance of A: ${this instanceof A}`)
  }
}

// B mixin
const B = c => class extends c {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from B] is instance of B: ${this instanceof c}`)
  }
}

// C mixin
const C = c => class extends c {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from C] is instance of C: ${this instanceof c}`)
  }
}

// D class, extends A class and B and C mixins
const ABC = extender(A).with(B,C)
class D extends ABC {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from D] is instance of D: ${this instanceof D}`)
  }
}

// E class, extends A class and C mixin
const AC = extender(A).with(C)
class E extends AC {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from E] is instance of E: ${this instanceof E}`)
  }
}

// F class, extends Object and B mixin
class F extends extender(Object).with(B) {
  foo() {
    if (super.foo) super.foo()
    console.log(`[from F] is instance of F: ${this instanceof F}`)
  }
}

// G class, wraps C mixin. Will use new decorator for instantiation, pretty format
class G extends extender(Object).with(C) {}

const d = new D()
const e = new E()
const f = new F()
const g = new G()
const h = new (extender(Object).with(B))

console.log(`Test D:
Class: D extends A, B and C
Instance of D: ${d instanceof D}`)
d.foo()
console.log('-')

console.log(`Test E:
Class: E extends A and C
Instance of E: ${e instanceof E}`)
e.foo()
console.log('-')
console.log(`Test F: 
Class: F extends B
Instance of F: ${f instanceof F}`)
f.foo()
console.log('-')
console.log(`Test G:
Class: G wrapped to use C alone with "new" decorator
Instance of G: ${g instanceof G}`)
g.foo()
console.log('-')

const isInstanceOfB = i => {
  try {
    return i instanceof B
  } catch (e) {
    return false
  }
}
console.log(`Test H:
Class: Anonymous class extends Object 
Instance of B: ${isInstanceOfB(h)}
Instance of Object: ${h instanceof Object}`)
h.foo()

This adds more readability, we can easily understand that we need a base class and on top of it we will add mixins using with.

Astrix answered 20/11, 2022 at 8:53 Comment(0)
S
0

This is my take on the problem. Instead of messing with prototypes, I "hoist"/"proxy" variables and functions up from the extended classes.

I do this by instantiating each base class in the constructor of the wrapping MultiClass (You can provide args for each class through super({"ClassName1":[args], "ClassName2":[args], ...})).

Then the code loops through each instantiated base class looking for properties to hoist. When it finds a function, it creates a mirror function to forward the return values. When it finds a variable, it creates getters and setters to access that variable.

Some interesting things with this method is that private variables won't get ruined like in other strategies. But because the base classes are running in their own scope, you can unfortunately get functions or variables with the same name running independently from each other. Name conflicts are overridden by the later class.

function MultiClass(...extendedClasses) {
    return class MultiClass {
        #baseClassMap = {};
        constructor(argMap) {
            // Loop through every base class
            extendedClasses.forEach(baseClass => {
                // Apply args to each base class based on argMap
                const baseClassArgs = argMap && argMap[baseClass.name];
                this.#baseClassMap[baseClass.name] = new baseClass(...baseClassArgs || []);

                // Gather every property until the Object constructor
                const properties = new Set();
                let obj = this.#baseClassMap[baseClass.name];
                while (obj.constructor !== Object) {
                    console.log(obj);
                    Object.getOwnPropertyNames(obj).forEach(propName => {
                        const propDescriptor = Object.getOwnPropertyDescriptor(obj, propName);
                        if (propDescriptor && (propDescriptor.configurable || typeof propDescriptor.configurable === "undefined") &&
                            (propDescriptor.writable || typeof propDescriptor.writable === "undefined")) {
                            properties.add(propName);
                            console.log(propName, propDescriptor);
                        }
                    });
                    obj = Object.getPrototypeOf(obj);
                }

                // Loop through every property
                properties.forEach(propName => {
                    // Hoist functions
                    if (typeof this.#baseClassMap[baseClass.name][propName] === "function" && propName !== "constructor") {
                        console.log("Setup function " + propName);
                        this[propName] = (...args) => this.#baseClassMap[baseClass.name][propName](...args);
                    }
                    // Hoist variables
                    else if (propName !== "constructor" && propName !== "length" && propName !== "prototype") {
                        console.log("Setup prop " + propName);
                        Object.defineProperty(this, propName, {
                            get: () => this.#baseClassMap[baseClass.name][propName],
                            set: (value) => this.#baseClassMap[baseClass.name][propName] = value
                        });
                    } else {
                        console.log("Skip prop " + propName);
                    }
                });
            });
        }
    }
}

Here's an example of usage:

class MyClass extends MultiClass(BaseClass1, BaseClass2, BaseClass3) {
    constructor() {
        // Optionally provide args to base class constructors
        super({
            "BaseClass1":[arg1, arg2],
            "BaseClass2":[arg1]
        });
    }
}
Switchman answered 9/8, 2023 at 7:52 Comment(0)
S
0

As shown in other answers, there is no real way to achieve multiple inheritance in JavaScript as of now. But like many other languages, multiple inheritance is more or less of a hack to achieve this.

For example, in python multiple inheritance is achieved by flattening inheritance into a chain of class layers.

Basically if you have:

class X(A, B):
    pass

You'll end up with a chain that looks like this:

X -> A -> B -> object

In Javascript, we can reproduce this by ensuring that classes are built on top of others.

In some other answer, they're trying to achieve something like this:

class X extends combine(A, B) {
}

The problem with this is that A and B classes are already built. So it's not possible to position one class over the other to create a chain.

For that we can use class expressions such as:

class Base {
  print() {
    console.log("base")
  }
}

A = (base) => class extends base {
  print() {
    super.print();
    console.log("IN A");
  }
};
B = (base) => class extends base {
  print() {
    super.print();
    console.log("IN B");
  }
};

Here we have 2 closure that can be passed a parent class to compose our class.

If we want to create a new type, we can simply do something like this.

N = A(B(Base))
M = B(A(Base))

So that covers how we can use expressions to build classes by passing our parent class to the next class being built. That's really the most important part to understand here.

Now let say we want to have something like this:

class Base {
}

class M1 {

  }

class M2 {
}

class A extends M1, Base {
}

class B extends M1, M2, Base {
}

In reality if we wanted to generate the class inheritance we'd have the following chain for A and B.

A = A -> M1 -> Base;
B = B -> M1 -> M2 -> Base;

So if we wanted to have multiple inheritance we'd need to have the following declarations instead.

Base = (base) => class {...}
M1 = (base) => class extends base {...}
M2 = (base) => class extends base {...}

And a function that looks like this:

function combine(...bases) {
    // reverse the order as the last base would be
    // the first that need to be created.
    bases.reverse() 
    let current_base = undefined;

    // for each base we create a composition of the current
    // with the previous one.
    for (let base of bases) {
        current_base = base(current_base)
    }

    return current_base
}

Then we can create a new class that combines the other using:

class X extends combine(M1, M2, Base) {
}

Obviously this can be extended by warping X into a constructor to be nested even more.

X = (base) => class extends combine(M1, M2, base) {...}

This way we can use combine to define the base class onto the X new class that we'd want to create.

Stalinsk answered 31/8, 2023 at 21:28 Comment(0)
P
0

Chong Lip Phang made a very helpful aggregation based on Sergio Carneiro's and Jon's implementation.

I want to make one tweak to Chong Lip Phang's work

When you call 'new mixin' make sure to pass the args (like this "new mixin(...args)") so that the necessary arguments are passed to the mixin's parent's (if any).

Updated code is below:

var aggregation = (baseClass, ...mixins) => {
    class base extends baseClass {
        constructor (...args) {
            super(...args);
            mixins.forEach((mixin) => {
                copyProps(this,(new mixin(...args))); // This line is the only change
            });
        }
    }
    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;
}
Persia answered 27/9, 2023 at 18:47 Comment(0)
C
0

I adapted https://stackoverflow.com/a/61860802/ into TypeScript, and this is working for me. (Thanks, @toddmo!)

type BaseClass = new () => void;

/**
 * Allows JavaScript class to extend multiple classes.
 */
export function Classes(baseClasses: BaseClass[]) {
  class Bases {
    constructor() {
      for (const baseClass of baseClasses) {
        Object.assign(this, new baseClass());
      }
    }
  }

  for (const base of baseClasses) {
    const properties = Object.getOwnPropertyNames(base.prototype).filter(
      (property) => property !== "constructor"
    );

    for (const property of properties) {
      // @ts-ignore
      Bases.prototype[property] = base.prototype[property];
    }
  }

  return Bases;
}

Use it like this:

export class Example extends Classes([
  ClassOne,
  ClassTwo,
]) {}
Convector answered 4/12, 2023 at 17:51 Comment(1)
This will likely fail for classes that use accessors (getters and setters) and will not properly account any [Symbol] properties. Look at Reflect.ownKeys() to get a list and use Object.getOwnPropertyDescriptor(s) and Object.defineProperty/ies() to handle the application. This is better than both Object.assign and any form of direct value assignment which would only grab the latest value of any given getter. And make sure you know what you're doing if you use big arrow functions at any point. They don't play as expected with accessorsKristikristian
C
-1

according to this: https://en.wikipedia.org/wiki/Multiple_inheritance#:~:text=Multiple%20inheritance%20is%20a%20feature,one%20particular%20object%20or%20class.', classes can't be extended by multiple classes, but they can be extended multiple times.

Try this:

const classExtend = (Inherit: any) =>
class extends Inherit{}

const classExtend2 = (Inherit: any) =>
class extends Inherit{}

const classExtend3 = (Inherit: any) =>
class extends Inherit{}

class classExtendAll extends classExtend3(classExtend2(classExtend1())){}
//or classExtend1(classExtend2(classExtend3())){}
//or classExtend2(classExtend3(classExtend1())){}
Chrissychrist answered 28/8, 2023 at 9:18 Comment(1)
Please translate this into EnglishEnvoi
N
-3

Here's an awesome/really crappy way of extending multiple classes. I'm utilizing a couple functions that Babel put into my transpiled code. The function creates a new class that inherits class1, and class1 inherits class2, and so on. It has its issues, but a fun idea.

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) {}
Nedrud answered 17/3, 2016 at 1:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.