3

I have been learning about ECMAScript 6 Classes, Mixins and other features for the past few days, but I'm not sure if my understanding of use cases is correct. Below is a snippet of an example with Classes, Subclasses and Mixins.

class Person{
    constructor (opts){
        for(let prop of Object.keys(opts)){
            this[prop] = opts[prop];
        }
      Person.count++;
    }

    static count = 0;
}

//Greeting Mixin
const Greetings = Person => class extends Person{
    sayHello(){}
}

//Job Mixin
const Jobs = Person => class extends Person{
    getJobs(){}
    getSalary(){}
    setJobs(){}
    setSalary(){}
}

//Subclass
class WorkingPerson extends Jobs(Greetings(Person)){
    constructor(opts){
        super(opts);
        //this.type = 'nice';
    }

    sayHello(){
        if(this.type == 'nice') 
            console.log(`Hello there! Wonderful day isnt it?`);
        else
            console.log(`Ah! Get out of here!`);
    }

    getJobs(){
        return this.jobs;
    }

    setJobs(...jobs){
        this.jobs.push(jobs);
    }

    getSalary(){
        return this.salary;
    }

    setSalary(salary){
        this.salary = salary;
    }
}

let wp = new WorkingPerson({name:'Ajay',jobs:['Digital Evangelist'],salary:10000});
let wp2 = new WorkingPerson({name:'Ron',jobs:['Entertainer'],salary:20000});
let wp3 = new WorkingPerson({name:'Morris',jobs:['Televangelist'],salary:30000});
console.log(`Number of people = ${Person.count}`);

There are no errors in the code above and I get the correct output. However, are my implementations of Mixins semantically correct? Does it make sense to have a Jobs and Greetings Mixin for the given context? I read a blog Mixins and Javascript: The Good, the Bad, and the Ugly. in which they define mixins as abstract subclasses. Looking at the examples, they added small functionality to a given class. Since this sounded similar to the definition of a Decorator, I looked the difference up to the accepted answer of Python: Use of decorators v/s mixins? .It says:

Mixins add new functionalities. Decorators are used to modify existing functionalities.

This got me thinking if the Jobs and Greetings are Mixins, then what example would you give for a decorator in this context? If I am wrong in any way, please provide code blocks of the correct answer.

Also, is there a better way to supply input arguements instead of throwing some raw object as a parameter while instantiating a WorkingPerson?

Community
  • 1
  • 1
Ajay H
  • 794
  • 2
  • 11
  • 28
  • I really don't see the point of `Greetings` and `Jobs`. They just define empty methods. Just use `class WorkingPerson extends Person` – Oriol Jan 02 '17 at 17:40
  • I thought the same. But if you were to create Mixins for the Person class, what would they be? – Ajay H Jan 02 '17 at 18:33
  • I don't like mixins. If I really needed multiple inheritance, [I would probably use a proxy](http://stackoverflow.com/a/31236132/1529630). – Oriol Jan 02 '17 at 18:47
  • 1
    @AjayH The question doesn't contain an example that needs mixins. In fact, class mixins are not so popular in JS and often indicate the problem with class design. – Estus Flask Jan 02 '17 at 19:10

2 Answers2

3

No, mixins are not applied correctly.

Person => class extends Person{...} is basic inheritance and it can be achieved accordingly:

class GreetingPerson extends Person {
    sayHello() {...}
}

If there are multiple unrelated classes that are supposed to have the same set of methods, consider applying composition over inheritance principle.

For the rest of the cases (e.g. polymorphism that cannot be refactored in accordance with mentioned principle) mixins can be applied to either prototype:

function mixin(obj) {
  return function (target) {
    for (const key of Object.getOwnPropertyNames(obj))
      if (key !== 'constructor')
        target.prototype[key] = obj[key];
    return target;
  }
}

class GreetingBeing {
  sayHello() {...}
}

const Person = mixin(GreetingBeing.prototype)(class Person extends Biped { ... })

// or for ES.Next,
// @mixin(GreetingBeing.prototype)
// class Person extends Biped { ...}

or instance:

class Person extends Biped {
  constructor()
    super();
    Object.assign(this, GreetingBeing.prototype);
  }
}

Notice that mixin helper/decorator doesn't do multiple inheritance. All it does is copying own enumerable properties from GreetingBeing.prototype to Person.prototype)

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • So here's a question on that: mixins in Scala for example, when you check the type, you get "TheClass with TheMixinClass" ("with" being the keyword to use the mixin capability). So you can differentiate an un-mixed class from a mixed one easly. Does something like this exist as straightforwardly in JS? – Tim Consolazio Jan 02 '17 at 20:49
  • 1
    @TimConsolazio Nope. There's no support for mixins/traits from the language, the one is on his/her own with them. In TypeScript, it is possible to designate such relationship with interfaces at compile time (not at runtime), like `@mixin(BazMixin.prototype) class Foo extends Bar implements BazMixin {...`. But this may lead to typing problems, because mixins got no love from TypeScript itself due to current restrictions. – Estus Flask Jan 02 '17 at 22:26
  • Yah that I'm aware of, I believe in fact that as time goes on you will see TS increasingly depart from ES6/7 in this way. Some features will be considered redundant or unnecessary (e.g. the problem with down leveling generators), overall the style of TS is much more Java like than say, Scala or python-like (which is what ES6 seems to be more like to me). – Tim Consolazio Jan 02 '17 at 23:39
  • @estus Just out of curiosity, why are you using `const` in the for loop? I understood that loop counters were one of the few places where it's preferable to use `let` over `const`, as it is meant to be reassigned on each iteration... – samson May 02 '18 at 02:44
  • @samson `let` should be used in `for` because of how it works. `for..of` allows to use `const` because it assigns a new variable on each iteration. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#Examples , *You can use const instead of let too, if you don't reassign the variable inside the block.* – Estus Flask May 02 '18 at 12:34
  • @estus Yeah, I saw that in the docs. I still don't see _why_ you would do it that way, though. I guess that's just the default position? If you _can_ use `const` then you _should_? – samson May 02 '18 at 20:02
  • 1
    @samson That's a matter of style. Yes, I consider this default position. The purpose of `const ` is to prevent accidental reassignment of a variable, so it's reasonable to use it wherever reassignment wasn't intended. This way it's possible to identify code where reassignment happens, this sometimes helps. You see `let` and expects that it will be assigned later. In any other case it would be unclear where `let` should be preferred over `const`. I assume there are JS style guides that solve this in another way. – Estus Flask May 02 '18 at 20:59
  • 1
    `getOwnPropertyNames` returns also the `constructor`. So the `mixin` function overwrites the constructor of the destination object. – ceving Dec 14 '20 at 11:42
  • @ceving Thanks for noticing, usually I add this check to this snippet when I use it. – Estus Flask Dec 14 '20 at 11:48
  • 1
    The `mixin` function does not copy getters correctly. In general it is necessary to use [`getOwnPropertyDescriptor`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) and [`defineProperty`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty) – ceving Dec 14 '20 at 12:06
  • Ignoring the constructor is also no option, because the initialization, which takes place in the constructor of the prototype mixed in, gets never executes. A mix-in function must rewrite the destination constructor. And this may change the number of arguments of the destination constructor, if the constructor of the source has any arguments. – ceving Dec 14 '20 at 12:27
1

Hmm..aren't decorators a proposal still? Could be wrong here but I thought this might be a future state (oh the speed at which we need to keep up...)

My understanding is this: in your context, say you wanted "jobs" to be read only. A decorator could fit the bill.

Something like:

function readonly ( target, key, descriptor ) {
    descriptor.writable = false;
    return descriptor;
}

// import the decorator function
import { readonly } from 'my-decorators';

class Person {
  @readonly
  person ( ) { return this.user; }
}

So we're modifying the behavior of that property here as implicitly read only, as opposed to hiding it in closures and whatnot. I also guess that now this property is inspectable as a "read only" property, such that it could appear in an IDE code assist dialog as read only (which is kind of cool).

One VERY good language to look for to understand mixins is Scala. The language itself may not be your thing, but mixins are an important and widely used part of the language. That's where I got my primary understanding anyway and it seemed effective. A less rigid, more flexible notion than straight OOP inheritance and such.

Great question I love exploring this stuff. You must be enjoying that class.

Tim Consolazio
  • 4,802
  • 2
  • 19
  • 28