Mixins para aulas ES6, transpilado com babel

De acordo com várias fonts ( 2ality , esdiscuss ), deve-se adicionar mixins às classs:

O EDIT descobriu que os methods de class não são enumeráveis, portanto, não podem funcionar. Editado o código abaixo, mas ainda sem alegria

class CartoonCharacter { constructor(author) { this.author = author; } drawnBy() { console.log("drawn by", this.author); } } // THIS CANNOT WORK // class methods are not enumerable // class Human { // haveFun() { // console.log("drinking beer"); // } // } let Human = Object.create({}, { haveFun: { enumerable: true, value: function () { console.log("drinking beer"); } } }); class Simpson extends Object.assign(CartoonCharacter, Human) { constructor(author) { super(author); } } let homer = new Simpson("Matt Groening"); homer.drawnBy(); // expected: drawn by Matt Groening homer.haveFun(); // expected: drinking beer 

Eu recebo o “desenhado por Matt Groening”, mas em vez do “beber cerveja” recebo um erro

 -> Uncaught TypeError: E.haveFun is not a function 

Existem dois problemas com seus mixins:

  1. Object.assign apenas copia propriedades enumeráveis ​​de um object. No entanto, os methods e propriedades de uma class não são enumeráveis.
  2. Os methods e propriedades de uma class não são definidos no construtor. Eles são definidos no protótipo do construtor.

É assim que você estenderia uma class usando mixins:

 class CartoonCharacter { constructor(author) { this.author = author; } drawnBy() { console.log("drawn by", this.author); } } class Human { haveFun() { console.log("drinking beer"); } } mixin(CartoonCharacter, Human); class Simpson extends CartoonCharacter { constructor(author) { super(author); } } let homer = new Simpson("Matt Groening"); homer.drawnBy(); // expected: drawn by Matt Groening homer.haveFun(); // expected: drinking beer function mixin(target, source) { target = target.prototype; source = source.prototype; Object.getOwnPropertyNames(source).forEach(function (name) { if (name !== "constructor") Object.defineProperty(target, name, Object.getOwnPropertyDescriptor(source, name)); }); } 

Funciona como esperado em Babel: demo .

Mixins de fábrica de subclass

Existe outra maneira de perceber mixins em Javascript: Com fábricas de subclass.

Uma subclass factory é uma function que excede uma class base e retorna uma subclass estendida desta class base:

 const mixin = base => class extends base { /* properties to mix in */ } 

Fábricas de subclasss são possíveis por dois motivos:

  • class es podem ser definidas como expressões e são de primeira class * em Javascript
  • cláusulas extends podem conter expressões arbitrárias

Vamos aplicar esse padrão:

 // superclass class CartoonCharacter { constructor(author) { this.author = author } drawnBy() { return "drawn by " + this.author } } // mixin 1 const Human = base => class extends base { haveFun() { return "drinking beer" } } // mixin 2 const Simpson = base => class extends base {} // composed subclass const Homer = Simpson(Human(CartoonCharacter)); // create an instance const homer = new Homer("Matt Groening") console.log(homer.drawnBy()); console.log(homer.haveFun()); 

Como já foi discutido anteriormente por @mfeineis, @adit-m-shah e @gotofritz, bem como mostrado com o último post do @ftor, sempre é preciso pensar cuidadosamente em bruxas que vão se tornar um “mixin” e como (re) montá-los em uma hierarquia que ainda poderia ser considerada um bom design. Esses exemplos mostram apenas que o JavaScript pode estar precisando de algo que se aproxime mais de traços do que o que já usamos (forçados) em vez disso. Algo como …

Objetos / Tipos Aplicáveis

  • que não misturam a syntax deles com a das classs,
  • porque eles devem trabalhar exclusivamente no nível de object / instância, assim também de dentro de construtores (de class) e de dentro dos corpos (function) de outros tipos aplicáveis, …
  • portanto, apenas sendo contêineres das Regras de Composição de Caracteres e Objetos que serão exclusivamente executados no tempo de aplicação de tal característica / ApplicableType .

A fim de provar tal conceito na prática, refatorei todos os exemplos acima na composição do traço a partir do traço e na composição do object a partir de traços dentro de construtores de class .

Eu gostaria de discutir tal abordagem, suas vantagens e suas deficiências em comparação com todas as técnicas já aceitas que estão em uso hoje.

 // - stateful applicable object/type proxy that does // link `this` context and injected local state. // const withExposeInternalStateProxy = Trait.create(function (use, applicator) { applicator(function (stateValue) { this.valueOf = function valueOf () { return Object.assign({}, stateValue); }; this.toString = function toString () { return JSON.stringify(stateValue); }; }); }); // - applicable object/type that just implements plain behavior. // - also known as: function based mixin ... kind of "FLight Mixin reloaded". // const withHumanBehavior = Trait.create(function (use, applicator) { function allowWeekness() { return "allow weekness"; } function showStrength() { return "show strength"; } function thinkFirst() { return "think first"; } function drinkingBeer() { return "drinking beer"; } applicator(function () { this.allowWeekness = allowWeekness; this.showStrength = showStrength; this.thinkFirst = thinkFirst; this.drinkingBeer = drinkingBeer; }); }); // - composite trait that uses its trait composition language ... trait from trait(s). // const withStrongBeerDrinkingHabit = Trait.create(function (use, applicator) { use(withHumanBehavior) .apply(withHumanBehavior, "drinkingBeer").as("haveFun"); //.apply.all.without("thinkFirst"); //.apply(withHumanBehavior).without("thinkFirst"); }); // - trait that defines a specialized use case of another behavior. // const withDoAcceptAlreadyExistingDrinkingHabit = Trait.create(function (use, applicator) { applicator(function () { var type = this; if (typeof type.haveFun !== "function") { withStrongBeerDrinkingHabit.call(type); } }); }); class CartoonCharacter { constructor(stateValue) { // linking `this` context and injected local state. withExposeInternalStateProxy.call(this, stateValue); } drawnBy() { return ("drawn by " + this.valueOf().author); } } class MaleSimpsonsType extends CartoonCharacter { constructor(stateValue) { super(stateValue); // object/type composition // withDoAcceptAlreadyExistingDrinkingHabit.call(super.constructor.prototype); // withStrongBeerDrinkingHabit.call(super.constructor.prototype); // do not always apply. // // withStrongBeerDrinkingHabit.call(this); // nope ...prototype is the far better choice. } sayHey() { return ("Hey, I am " + this.valueOf().name); } } const homer = new MaleSimpsonsType({ name: "Homer Simpson", author: "Matt Groening" }); console.log("\n"); console.log('(homer + "") : ', (homer + "")); console.log('homer.valueOf() : ', homer.valueOf()); console.log("\n"); console.log('homer.drawnBy() : ', homer.drawnBy()); console.log('homer.haveFun() : ', homer.haveFun()); console.log('homer.sayHey() : ', homer.sayHey()); console.log("\n"); console.log('Object.keys(homer) : ', Object.keys(homer)); console.log('homer.constructor : ', homer.constructor); console.log('Object.keys(homer.constructor.prototype) : ', Object.keys(homer.constructor.prototype)); console.log('homer.constructor.prototype : ', homer.constructor.prototype); console.log('(homer instanceof MaleSimpsonsType) : ', (homer instanceof MaleSimpsonsType)); console.log('(homer instanceof CartoonCharacter) : ', (homer instanceof CartoonCharacter)); 
 .as-console-wrapper { max-height: 100%!important; top: 0; } 
  
    Intereting Posts