Класс ES6 Множественное наследование


134

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

Мне интересно, поддерживает ли ES6 множественное наследование так же, как это делают другие языки с утиным типом. Например, могу я сделать что-нибудь вроде:

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

расширить несколько классов до нового класса? Если да, то предпочтет ли интерпретатор методы / свойства из ClassTwo над ClassOne?


4
Это на самом деле не возможно с текущим способом наследования работает в JS, ближайший вы можете сделать , это Mixin
qwertymk

Можете ли вы предоставить какую-то ссылку, в которой говорится, что это невозможно в новой спецификации, и если да, можете ли вы дать ответ, чтобы я мог его принять?
BTC

Я читал, что новые классы ES6 не добавляют никаких новых функций, они просто синтаксический сахар.
Oriol


@Oriol, это синтаксический сахар, но я задавался вопросом, делает ли этот сахар что-то с несколькими классами внутри.
BTC

Ответы:


70

У объекта может быть только один прототип. Наследование от двух классов может быть выполнено путем создания родительского объекта как комбинации двух родительских прототипов.

Синтаксис создания подклассов позволяет сделать это в объявлении, поскольку правая часть extendsпредложения может быть любым выражением. Таким образом, вы можете написать функцию, которая объединяет прототипы в соответствии с любыми критериями, которые вам нравятся, и вызывать эту функцию в объявлении класса.


1
Меня всегда интересовало, есть ли способ установить геттер для __proto__ссылки, чтобы перенаправить поиск опоры на правильный объект? Я пробовал, но так и не смог заставить его работать
qwertymk

3
@qwertymk хорошо помните, что __proto__это устаревшая функция. Он отражает внутреннюю ссылку на прототип, но на самом деле это не внутренняя ссылка на прототип.
Pointy

так что никогда не будет шансов, что какой-нибудь подобный взлом когда-нибудь сработает? core-js сделал нечто подобное с поддержкой weakmap с помощью геттеров. Множественное наследование было бы очень круто
qwertymk

1
@qwertymk ну, я не могу с уверенностью сказать, действительно ли это невозможно. Лично я использую наследование в JavaScript очень и очень редко. На самом деле я довольно редко использую прототипы, если на то пошло.
Pointy

2
Вот решение, которое я придумал: esdiscuss.org/topic/symbol-for-modifying-property-lookup . Пример: class Foo extends new MultiClass(Bar, Baz, One, Two) { ... }. Методы и свойства последнего переданного конструктора new MultiClassимеют наивысший приоритет, они просто смешиваются с новым прототипом. Я думаю, что существует еще лучшее решение, если оно будет повторно реализовано с использованием прокси ES6, но для него пока недостаточно встроенной поддержки.
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 (Object), делая B распространяется (B||Object).
Аарон

@Aaron, я не совсем уверен, что следую за вами в этом (или вы следите за мной). Если F extends (B||Object)вместо F extends B(Object), он расширит миксин B как он (как функцию), поэтому 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() {для более красивого использования, но более уродливого взлома Kappa
Poelinca Dorin

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());использования, если вы этого захотите.
Poelinca Dorin

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, это не работает. просто к вашему сведению всем, кто мог бы захотеть это для этой цели.
r3wt

Это перезаписывает переменные и функции с одинаковыми именами.
Винсент Хох-Драй

17

Джастин Фагнани описывает очень чистый (imho) способ объединения нескольких классов в один, используя тот факт, что в 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

Вы можете использовать миксины, чтобы получить некоторые из этих функций, но вы не получите позднего связывания:

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, потому что он говорил о ключевом слове extends, вокруг которого был поставлен вопрос, а не о шаблонах наследования, но спасибо за проявленный интерес!
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 с флагом --harmony


Я не думаю, что вам нужен флаг гармонии для узла 4x и выше.
Умайр

2

используйте миксины для множественного наследования 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? В вашем примере показано, что один класс наследует от двух, но связанных классов. Это одиночное наследование, а не множественное наследование.
vlad-ardelean

@ vlad-ardelean На самом деле отношение искусственное, т.е. устанавливается динамически по вызову classTwo. В любом случае, не имея подлинной концепции класса, JS не имеет структурного наследования. Навскидку, я не могу представить себе сценарий JS, в котором миксины ведут себя иначе, чем вы ожидаете, концептуализируя их как MI из истинного объектно-ориентированного мира (кроме определенной «супер-цепочки»); может быть, кто-нибудь более осведомленный, чем я, сможет его предоставить.
collapsar

@collapsar Думаю, вы абсолютно правы. JS имеет прототипное наследование, то есть существует цепочка прототипов, в которой каждый прототип в цепочке имеет одного единственного родителя. При смешивании целого набора классов в цепочку прототипов в определенном порядке это фактически то же самое, что и MI в мире объектно-ориентированных приложений.
Стейн де Витт,

2

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

class Rectangle расширяет агрегацию (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

Что ж, 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()у sharkсозданного есть biteметод, в то время как только его прототип имеет eatметод


Это не сработает. Методы-прототипы не будут смешаны и привязка будет неправильной.
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 сработало для меня:

многооконном 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())

Доходность в браузере-консоли:

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

ES6 - это JavaScript!
Bergi

1

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

Вкратце, вот как можно реализовать MI в 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();

А вот однострочник specialize_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 работает с прототипом, вы не можете использовать более 1 наследования для одного класса, но вы можете агрегировать и объединять свойство объекта 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

Вот отличный / действительно дрянной способ расширения нескольких классов. Я использую пару функций, которые Babel вложил в мой переданный код. Функция создает новый класс, который наследует 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.