javascript - ES6-klasse flere arv


Jeg har gjort mesteparten av min research om dette på BabelJS og på MDN (som ikke har noen informasjon i det hele tatt), men vær så snill å si ifra dersom du mener jeg ikke har vært nøye nok med å lete etter mer informasjon om ES6-spesifikasjonen.

Jeg lurer på om ES6 støtter flere arv på samme måte som andre duck-typed språk gjør. For eksempel, kan jeg gjøre noe slikt:

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

å utvide flere klasser til den nye klassen? Hvis ja, vil tolken foretrekke metoder/egenskaper fra ClassTwo over ClassOne?

Svar #1

Sjekk mitt eksempel nedenfor, super-metoden fungerer som forventet. Med noen få triks fungerer til og med instanceof (for det meste):

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

Vil skrive ut

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

Lenke til å leke rundt

Svar #2

Et objekt kan bare ha ett prototype. Å arve fra to klasser kan gjøres ved å lage et overordnet objekt som en kombinasjon av to overordnede prototyper.

Syntaksen for subklassing gjør det mulig å gjøre det i deklarasjonen, siden høyresiden av extends-klausulen kan være hvilket som helst uttrykk. Dermed kan du skrive en funksjon som kombinerer prototyper i henhold til hvilke som helst kriterier du ønsker, og kalle den funksjonen i klassedeklarasjonen.

Svar #3

Mitt svar ser ut som mindre kode og det fungerer for meg:

    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!

Svar #4

Justin Fagnani beskriver en veldig ren (etter min mening) måte å sette sammen flere klasser til én på ved å bruke det faktum at i ES2015 kan klasser opprettes med klasseuttrykk.

Uttrykk vs deklarasjoner

I utgangspunktet, akkurat som du kan lage en funksjon med et uttrykk:

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

du kan gjøre det samme med klasser:

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

Uttrykket evalueres ved kjøretid, når koden kjøres, mens en deklarasjon utføres på forhånd.

Bruke klasseuttrykk for å lage miksiner

Du kan bruke dette til å lage en funksjon som dynamisk oppretter en klasse bare når funksjonen kalles:

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

Det kule med det er at du kan definere hele klassen på forhånd og bare bestemme hvilken klasse den skal utvide når du kaller funksjonen:

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

Hvis du vil blande flere klasser sammen, fordi ES6-klasser kun støtter enkelt arv, må du lage en kjede av klasser som inneholder alle klassene du vil blande sammen. Så la oss si at du vil lage en klasse C som utvider både A og B, kan du gjøre dette:

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

Problemet med dette er at det er veldig statisk. Hvis du senere bestemmer deg for at du vil lage en klasse D som utvider B men ikke A, har du et problem.

Men med litt smart trikseri ved å bruke det faktum at klasser kan være uttrykk, kan du løse dette ved å lage A og B ikke direkte som klasser, men som klassefabrikker (ved å bruke pilfunksjoner for korthet):

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)

Legg merke til hvordan vi bare bestemmer på siste øyeblikk hvilke klasser som skal inkluderes i hierarkiet.

Jeg laget et bibliotek basert på disse prinsippene som du kan ta en titt på: mics

Svar #5

Implementeringen til Sergio Carneiro og Jon krever at du definerer en initialiseringsfunksjon for alle unntatt én klasse. Her er en modifisert versjon av aggregeringsfunksjonen, som i stedet gjør bruk av standardparametere i konstruktørene. Inkludert er også noen kommentarer fra meg.

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

Her er en liten 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.

Denne aggregeringsfunksjonen vil foretrekke egenskaper og metoder til en klasse som vises senere i klasselisten.

Relaterte artikler