ES 6 Classes - Mixins

2019-01-29 09:44发布

问题:

I'm coming up with View (HTML markup) and Utility (JavaScript - behavior) architecture and creating atomic classes for composing views and utilities using ES6 Class. There will be a need that multiple utility classes can be composed/mixed into a single view class.

How can ES6 Class API provide a way to mix-in class(es) into another/main class. I've looked at Object.assign but that is for objects and not at class-level.

回答1:

JavaScript classes right now and hopefully also in future only can be extended from each other but can not be mixed into one another. If at all, then most probably Lightweight Traits do make it into the specification one day.

Its architectural approach is specific to JavaScript. It has been mentioned quite often in the last few years ... esdiscuss.org: »about lightweight traits«, github.com/WebReflection: »features :: with«, webreflection.blogspot.com: »A future friendly, backward compatible, class utility«, reddit.com/r/javascript: »Functional Mixins in ECMAScript 2015«, raganwald.com: »Functional Mixins in ECMAScript 2015« ... and possibly is best compared to Angus Croll's Flight Mixins.

Pure function based Mixin/Trait approaches ... This is not an essay about 'Traits in Javascript', The many »Talents« of JavaScript ... do come closest to what the OP has ask for unless something similar to ...

//  proposed trait syntax ...       //  ... desugared e.g. to ...

trait Enumerable_first_last {       //  var Enumerable_first_last = (function () {
  // trait body.                    //    // mixin module.
                                    //
  const                             //    var
    FIRST = function () {           //      first = function () { // shared code.
      return this[0];               //        return this[0];
    },                              //      },
    LAST = function () {            //      last = function () {
      return this[this.length - 1]; //        return this[this.length - 1];
    }                               //      }
  ;                                 //    ;
                                    //
  applicator () {                   //    return function Enumerable_first_last () {
    // applicator body.             //      // mixin body.
                                    //
    this.first = FIRST;             //      this.first = first; // referencing ...
    this.last = LAST;               //      this.last = last;   // ...  shared code.
  }                                 //    };
                                    //
}                                   //  }());

...

//  proposed trait syntax ...       //  ... desugared e.g. to ...

trait Enumerable_item {             //  var Enumerable_item = (function () {
                                    //
  const                             //    var
    ITEM = function (idx) {         //      item = function (idx) {
      return this[                  //        return this[
        Math.floor(                 //          Math.floor(
          parseFloat(idx, 10)       //            parseFloat(idx, 10)
        )                           //          )
      ];                            //        ];
    }                               //      }
  ;                                 //    ;
                                    //
  applicator () {                   //    return function Enumerable_item () {
                                    //
    this.item = ITEM;               //      this.item = item;
  }                                 //    };
                                    //
}                                   //  }());

...

//  proposed trait syntax ...       // ... desugared e.g. to ...

trait Enumerable_first_last_item {  //  var Enumerable_first_last_item = (function () {
                                    //
  use Enumerable_first_last;        //    return function Enumerable_first_last_item() {
  use Enumerable_item;              //
/*                                  //      Enumerable_first_last.call(this);
  applicator () {                   //      Enumerable_item.call(this);
    // can be omitted if empty.     //    };
  }*/                               //
}                                   //  }());

...

                                      //  ... desugared e.g. to ...
                                      //
class Queue {                         //  var Queue = (function () {
                                      //
//use Allocable;                      //    return function Queue () {
  use Observable;                     //      var list = [];
                                      //
  constructor () {                    //      this.enqueue = function (type) {
    const list = [];                  //
                                      //        list.push(type);
    this.enqueue = function (type) {  //        return type;
                                      //      };
      list.push(type);                //      this.dequeue = function () {
      return type;                    //
    };                                //        return list.shift();
    this.dequeue = function () {      //      };
                                      //
      return list.shift();            //    //Allocable.call(this, ...);
    };                                //      Observable.call(this);
  }                                   //    };
                                      //
}                                     //  }());
                                      //
var q = new Queue;                    //  var q = new Queue;
                                      //
q.enqueue(9);                         //  q.enqueue(9);
q.enqueue(8);                         //  q.enqueue(8);
q.enqueue(7);                         //  q.enqueue(7);
                                      //
console.log(q.dequeue());             //  console.log(q.dequeue());
console.log(q.dequeue());             //  console.log(q.dequeue());
console.log(q.dequeue());             //  console.log(q.dequeue());
                                      //
console.log(q);                       //  console.log(q);
console.log(Object.keys(q));          //  console.log(Object.keys(q));

... was shipped to ECMAScript land.



回答2:

There is a really nice pattern with ES2015 classes (which I don't necessarily endorse as such) to create mixins by Sebastian Markbage, which I've slightly adapted.

The utility function mixinClasses can be used to mix in class factories (aka factory functions that return classes) into your new class:

function mixinClasses(...mixins) {
  // TODO: Add all possible method names that might call super()
  // to the base class so that they don't throw.
  return mixins.reduce((base, mixin) => {
      return mixin(base);
  }, class {});
}

Which can be used as follows, for example with the two factory functions Foo and Bar:

const Foo = base => class extends base {
  myFn() {
  }
};

const Bar = base => class extends base {
  myFn() {
    super.myFn();
  }
};

class Baz extends mixinClasses(Foo, Bar) {
  myFn() {
    super.myFn();
  }
}