Клас ES6 Множинне успадкування


134

Я провів більшу частину своїх досліджень з цього приводу на BabelJS та MDN (який взагалі не має інформації), але, будь ласка, не соромтесь сказати мені, якщо я не насторожився достатньо уважно, щоб оглянути детальнішу інформацію про ES6 Spec.

Мені цікаво, підтримує чи ні ES6 багатократне успадкування так само, як це роблять інші мови, що набираються качками. Наприклад, чи можу я зробити щось на кшталт:

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

щоб розширити кілька класів на новий клас? Якщо так, то чи буде перекладач віддавати перевагу методам / властивостям від ClassTwo над ClassOne?


4
Це на самому ділі не можливо з поточним способом успадкування працює в JS, найближчий ви можете зробити , це Mixin
qwertymk

Чи можете ви надати якусь посилання, в якій зазначається, що це неможливо в новій специфікації, і якщо так, чи можете ви зробити це відповіддю, щоб я міг його прийняти?
BTC

Я читаю, що нові класи ES6 не додають жодної нової функціональності, вони просто синтаксичний цукор.
Оріол


@Oriol, вони є синтаксичним цукром, але я замислювався, чи цей цукор щось робить із кількома класами всередині.
BTC

Відповіді:


70

Об'єкт може мати лише один прототип. Успадкування з двох класів можна зробити, створивши батьківський об'єкт у вигляді комбінації двох батьківських прототипів.

Синтаксис підкласифікації дозволяє зробити це в декларації, оскільки права частина цього extendsпункту може бути будь-яким виразом. Таким чином, ви можете написати функцію, яка поєднує прототипи за будь-якими критеріями, які вам подобаються, і викликати цю функцію в декларації класу.


1
Мене завжди цікавили, чи є спосіб встановити геттер на __proto__посилання, щоб переслати пошук опори на потрібний об'єкт? Я намагався, але ніколи не
змусив

3
@qwertymk добре пам’ятайте, що __proto__сама по собі є застарілою функцією. Він відображає внутрішнє прототипове посилання, але насправді це не внутрішнє прототипове посилання.
Пойнті

так що ніколи не виникне жодного шансу на такий хак? core-js зробив щось подібне з підтримкою слабкої карти за допомогою getters. Багаторазове успадкування було б дуже круто
qwertymk

1
@qwertymk добре, я не можу з владою сказати, чи це точно неможливо. Особисто я використовую спадщину в JavaScript дуже і дуже рідко. Насправді я використовую прототипи досить рідко.
Поні

2
Ось таке рішення, яке я придумав: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Приклад: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Способи та властивості останнього конструктора, які перейшли до new MultiClassнайвищого пріоритету, вони просто змішані з новим прототипом. Я думаю, що ще краще рішення існує, якщо його повторно реалізувати за допомогою ES6 Proxies, але для цього ще недостатньо нашої підтримки.
trusktr

89

Перевірте мій приклад нижче, superметод працює як очікувалося. Використання кількох хитрощів навіть instanceofпрацює (більшість часу):

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

Буде роздруковано

Тест D: розширює A, B, C -> зовнішній екземпляр D: true
від A -> внутрішній екземпляр A: true
від B -> внутрішній екземпляр B: true
від C -> внутрішній екземпляр C: true
від D -> внутрішній екземпляр D: true
-
Тест E: розширює A, C -> зовнішній екземпляр E: true
від A -> внутрішній екземпляр A: true
від C -> внутрішній екземпляр C: true
від E -> внутрішній екземпляр E: true
-
Тест F: розширює B -> зовнішній екземпляр F: true
від B -> внутрішній екземпляр B: true
від F -> внутрішній екземпляр F: true
-
Тест G: обгортка для використання C окремо з "новим" декоратором, гарний формат -> зовнішній екземпляр G: true
від C -> внутрішній екземпляр C: true
-
Тест B один, некрасивий формат "new (B (Object))" -> зовнішній екземпляр B: false, цей не вдається
від B -> внутрішній екземпляр B: true

Посилання на загадку навколо


1
Ви можете виправити цей "потворний формат" B (Об'єкт), зробивши B розширенням (B||Object).
Аарон

@Aaron Я не дуже впевнений, що я слідкую за тобою на цьому (або ти слідкуєш за мною). Якщо F extends (B||Object)замість цього F extends B(Object), він поширить B mixin так, як він (як функцію), то F поширить прототип лише за замовчуванням Функція за замовчуванням, оскільки B ніколи не виконувався. Використовуючи, F extends B(Object)ми фактично виконуємо функцію B, і F поширює функцію "що завгодно", повертається функція B, в цьому випадку це клас B, визначений всередині функції B ... невеликий хак, щоб зберегти правильне іменування класу.
Poelinca Dorin

@Aaron, що ми могли б зробити, це використовувати параметри за замовчуванням функції, const B = (B = Object) => class extends B {а потім використовувати class F extends B() {для
кращого

const B = (B) => class extends (B||Object) {дозволить вам замінити inst5 = new (B(Object)); // instance only B, ugly formatз inst5 = new (B());, або , можливо , я неправильно зрозумів контекст ...
Аарон

@Aaron так, це буде добре, поки console.log('from B -> inside instance of B: ${this instanceof B}');відьма не вдасться Right-hand side of 'instanceof' is not an object. Використання, const B = (B = Object) => class extends B {як згадувалося раніше, пройде instanceof тест і надасть вам можливість inst5 = new (B());використання, якщо ви цього хочете.
Поелінка Дорін

23

Реалізація Серхіо Карнейро та Йона вимагає визначити функцію ініціалізатора для всіх, крім одного класу. Ось модифікована версія функції агрегації, яка використовує параметри за замовчуванням у конструкторах. Також є деякі коментарі від мене.

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

Ось трохи демонстрації:

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.

Ця функція агрегації надасть перевагу властивостям та методам класу, які з’являться пізніше у списку класів.


3
коли я намагаюся використовувати це з реагувати Component, це не працює. просто FYI будь-кому іншому, хто, можливо, хотів цього для цієї мети.
r3wt

Це перезаписує змінні та функції, що мають однакове ім'я.
Вінсент Хох-Дрей

17

Джастін Фаньяні описує дуже чистий (імхо) спосіб складання декількох класів в один, використовуючи той факт, що в ES2015 класи можуть створюватися з виразами класів .

Вирази проти декларацій

В основному, так само, як ви можете створити функцію з виразом:

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

ви можете зробити те ж саме з класами:

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

Вираз оцінюється під час виконання, коли виконується код, тоді як декларація виконується заздалегідь.

Використання виразів класів для створення міксин

Ви можете використовувати це для створення функції, яка динамічно створює клас лише тоді, коли функція викликається:

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

Класна річ у тому, що ви можете заздалегідь визначити весь клас і вирішити лише, на який клас він повинен поширюватися на час виклику функції:

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

Якщо ви хочете змішати кілька класів разом, оскільки класи ES6 підтримують лише одне успадкування, вам потрібно створити ланцюжок класів, який містить усі класи, які ви хочете змішати разом. Отже, скажімо, що ви хочете створити клас C, який розширює і A, і B, ви можете це зробити:

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

Проблема в тому, що це дуже статично. Якщо ви згодом вирішите, що хочете скласти клас D, який розширює B, але не A, у вас є проблеми.

Але за допомогою розумного хитрості, використовуючи той факт, що класи можуть бути виразами, ви можете вирішити це, створивши A і B не безпосередньо як класи, а як фабрики класів (використовуючи функції стрілок для стислості):

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)

Зверніть увагу, як ми лише в останній момент вирішуємо, які класи включати до ієрархії.


8

Це насправді неможливо з тим, як працює прототипне успадкування. Давайте подивимось, як працюють успадковані реквізити у 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

давайте подивимося, що станеться, коли ви отримаєте доступ до опори, яка не існує:

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

Ви можете використовувати mixins, щоб отримати частину цієї функціональності, але ви не отримаєте пізньої прив'язки:

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

проти

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

Приймаючи відповідь @ Pointy, тому що він розповідав про ключове слово розширення, яке саме було поставлено навколо справжнього питання, а не моделей успадкування, але дякую, що зацікавилися!
BTC

2

Я придумав таке рішення:

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

використання:

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

Поки ви робите ці трюки зі своїми спеціально написаними класами, її можна прикувати. але ми незабаром, коли вам захочеться розширити якусь функцію / клас, написаний не так - у вас не буде шансів продовжити цикл.

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

працює для мене у вузлі v5.4.1 з прапором --гармонія


Я не думаю, що вам потрібен прапор гармонії для вузла 4x і вище.
Умейр

2

На сторінці es6-features.org/#ClassInheritanceFromExpressions можна записати функцію агрегації, щоб дозволити багаторазове успадкування:

Прямокутник класу розширює агрегацію (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
}

Але це вже передбачено в бібліотеках, як агрегація .


1

використовувати Mixins для багаторазового спадкування ES6.

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

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

3
чи не повинно означати багаторазове успадкування one class inherits from 2 or more unrelated classes? Як показує ваш приклад, один клас успадковує від 2-х, але пов'язаних класів. Це поодиноке успадкування, а не множинне спадкування.
vlad-ardelean

@ vlad-ardelean Власне відношення штучне, тобто. встановлюється динамічно шляхом виклику classTwo. Не маючи справжньої концепції класу, JS так чи інакше не має структурної спадщини. Я не можу уявити сценарій JS, коли міксин поводить себе інакше, ніж ви очікували б концептуалізувати їх як ІМ з справжнього світу OO (крім визначеного "супер'-ланцюга"); можливо, якась людина, більш обізнана, ніж я, може її поставити.
колапс

@collapsar Я думаю, ви абсолютно праві. JS має прототипне успадкування, тобто існує прототип ланцюга, де кожен прототип ланцюга має одного єдиного батьківського. При змішуванні цілого ряду класів у ланцюзі прототипу у визначеному порядку, він фактично такий самий, як ІМ у світі OO.
Штійн де Вітт

1

Добре Object.assign дає вам можливість зробити щось близьке, хоч трохи схожіше на композицію з класами ES6.

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

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

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

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

Я ніде не бачив цього використовуваного, але насправді він дуже корисний. Ви можете використовувати function shark(){}замість класу, але є переваги використання класу.

Я вважаю, що єдине, що відрізняється від успадкування з extendключовим словом, - це те, що функція не працює лише наprototype об'єкті, а й на сам об'єкт.

Таким чином , тепер , коли ви створили є метод, в той час як тільки його прототип має методnew Shark()sharkbiteeat


Це не спрацює. Методи прототипу не будуть змішані, і зв'язування буде неправильним.
jonschlinkert

1

Немає простого способу зробити успадкування кількох класів. Я дотримуюся поєднання асоціації та успадкування для досягнення такого роду поведінки.

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

Сподіваюся, що це корисно.


1

Це рішення ES6 працювало для мене:

множинне спадкування.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())

Вихід на консолі браузера:

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

ES6 - JavaScript!
Бергі

1

Я витратив пів тижня, намагаючись розібратися в цьому, і написав про це цілу статтю https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS , і сподіваюся, що це допоможе комусь із вас.

Коротше, ось як ІМ можна реалізувати в 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();

А ось спеціалізований_with () однолінійний:

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

Ще раз перегляньте https://github.com/latitov/OOP_MI_Ct_oPlus_in_JS .


1

у javascript ви не можете надати класу (конструктор функція) 2 різних об'єкта прототипу, і тому, що при спадкуванні в роботі Javascript з прототипом Soo ви не можете використовувати більше одного успадкування для одного класу, але ви можете об'єднати та приєднати властивість об'єкта Prototype та це основне властивість всередині класу вручну з рефакторингом цього батьківського класу, а далі поширює цю нову версію та приєднав клас до цільового класу, має код вашого запитання:

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

    class AggregatorClass {

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

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

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

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


    return AggregatorClass

};

1

Моя відповідь здається меншим кодом, і вона працює для мене:

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

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

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

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

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

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


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

0

використовувати ступінь за допомогою спеціальної функції для обробки декількох успадкувань за допомогою es6

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

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

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

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

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

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


0

Я додам і своє рішення - я вважаю це найбільш привітним для себе з того, що прочитав у цій темі.

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

Ви можете використовувати його тоді так:

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

0

Як доказ концепції, я виконував таку функцію. Він займає список класів і компонує їх у новий клас (останній прототип виграє, щоб не було конфліктів). Під час створення складеної функції користувач може вибрати всі оригінальні конструктори [ sic! ] або пройти своє. Це було найбільшою проблемою цього експерименту: скласти опис того, що повинен робити конструктор. Копіювання методів у прототип не є проблемою, а якою є призначена логіка новоскладеного об'єкта. А може, це має бути безконструкторно? У Python, з того, що я знаю, він знаходить відповідність конструктор, але функції в JS більше сприймають, отже, можна переходити до функції майже все, і з підпису не буде зрозуміло.

Я не думаю, що це оптимізовано, але метою було вивчення можливостей. instanceofне буде поводитись так, як очікувалося, що, я думаю, є обривом, оскільки розробники, орієнтовані на клас, люблять використовувати це як інструмент.

Можливо, у JavaScript просто його немає.

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

Спочатку розміщено тут (gist.github.com).



-3

Ось дивовижний / насправді хитрий спосіб розширення кількох класів. Я використовую пару функцій, які Бабель вклала в мій перекладений код. Функція створює новий клас, який успадковує class1, а class1 успадковує class2 тощо. У нього є свої питання, але весела ідея.

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) {}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.