30

In an ES6 class with some instance variables and methods, how can you add a mixin to it? I've given an example below, though I don't know if the syntax for the mixin object is correct.

class Test {
  constructor() {
    this.var1 = 'var1'
  }
  method1() {
    console.log(this.var1)
  }
  test() {
    this.method2()
  }
}

var mixin = {
  var2: 'var2',
  method2: {
    console.log(this.var2)
  }
}

If I run (new Test()).test(), it will fail because there's no method2 on the class, as it's in the mixin, that's why I need to add the mixin variables and methods to the class.

I see there's a lodash mixin function https://lodash.com/docs/4.17.4#mixin, but I don't know how I could use it with ES6 classes. I'm fine with using lodash for the solution, or even plain JS with no libraries to provide the mixin functionality.

user779159
  • 9,034
  • 14
  • 59
  • 89
  • ... one could choose an approach of for example _**function based (context aware) mixins**_ like it gets demonstrated with e.g. 1) [What are flexible approaches for creating different kinds or types of shape objects like rect- or triangles?](https://stackoverflow.com/questions/69050958/what-are-flexible-approaches-for-creating-different-kinds-or-types-of-shape-obje/69089680#69089680), ... – Peter Seliger Aug 24 '22 at 13:07
  • ... 2) [How could one refactor the implementation of two different types of sub classes while keeping both, functionality and naming of their methods?](https://stackoverflow.com/questions/69729647/how-could-one-refactor-the-implementation-of-two-different-types-of-sub-classes/69730714#69730714), ... 3) [How to properly replace 'extends', using functional programming?](https://stackoverflow.com/questions/61046298/how-to-properly-replace-extends-using-functional-programming/61688193#61688193) – Peter Seliger Aug 24 '22 at 13:07

4 Answers4

39

Javascript's object/property system is much more dynamic than most languages, so it's very easy to add functionality to an object. As functions are first-class objects, they can be added to an object in exactly the same way. Object.assign is the way to add the properties of one object to another object. (Its behaviour is in many ways comparable to _.mixin.)

Classes in Javascript are only syntactic sugar that makes adding a constructor/prototype pair easy and clear. The functionality hasn't changed from pre-ES6 code.

You can add the property to the prototype:

Object.assign(Test.prototype, mixin);

You could add it in the constructor to every object created:

constructor() {
    this.var1 = 'var1';
    Object.assign(this, mixin);
}

You could add it in the constructor based on a condition:

constructor() {
    this.var1 = 'var1';
    if (someCondition) {
        Object.assign(this, mixin);
    }
}

Or you could assign it to an object after it is created:

let test = new Test();
Object.assign(test, mixin);
lonesomeday
  • 233,373
  • 50
  • 316
  • 318
  • @lonesomeday What's the difference between adding it to the prototype of to the object itself? I thought when declaring methods it's better to do it on the prototype because then all objects share the same function, rather than them all having their own functions which do the same thing. Does that also apply for this mixin technique, where it's more efficient to apply it to the prototype than the object? – user779159 Feb 15 '17 at 13:28
  • 1
    @user779159 The function will only exist once. It's only if you are declaring the function again that a second function will be created. (E.g. if you have a decorator function that creates a new function every time it is run.) – lonesomeday Feb 15 '17 at 13:43
  • 3
    @lonesomeday Out of interest, if the mixin was declared in a different file to the Object, what would that look like. as in the exports on the mixin file and the require on the object file (or where the object is declared) – Jarede Feb 14 '18 at 12:51
  • 3
    Would be nice if this showed how to do a mixin where the thing you're mixing in is an ES6 class itself (e.g. both target and source are ES6 classes). It appears to me that `Object.assign()` does not work for that probably because ES6 methods are not, by default, iterable. – jfriend00 Sep 05 '19 at 23:05
20

In es6 you can do this without assigning and you can even invoke the mixin constructor at the correct time!

http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/#bettermixinsthroughclassexpressions

This pattern uses class expressions to create a new base class for every mixin.

let MyMixin = (superclass) => class extends superclass {
  foo() {
    console.log('foo from MyMixin');
  }
};
class MyClass extends MyMixin(MyBaseClass) {
  /* ... */
}
Jamie Pate
  • 1,783
  • 20
  • 18
  • 8
    Unfortunately, that article provides false information. He states that a MixIn provides an affirmative to "instanceOf()"; which is not correct. That same misconception under-lies his whole technique; he is lengthening the inheritance chain. MixIns join a Class from the horizontal direction, but he is extending the inheritance chain vertically. – IAM_AL_X Oct 02 '19 at 03:25
  • 1
    I would argue that extending the inheritance chain is the better way to do it. instanceof is just a side benefit, and requires some fancy footwork. Otoh with this approach you are able to run constructor logic in a controlled fashion, which is the main benefit. – Jamie Pate Oct 02 '19 at 15:21
  • 4
    Inheritance and Mix-ins are two different operations. In most languages (those who have no idea how to solve the [diamond problem](https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem)), you can inherit only from one super-class. But in every language you can mix-in as many classes as you want. – ceving Dec 14 '20 at 11:28
  • Either way is a perfectly valid way to define a language feature that allows you to add common functionality to a number of classes and the article makes perfectly clear what semantics is being offered so there is no danger of confusion. Also, the semantics aren't really different than module includes in ruby which are called mixins (here you overwrite methods from other modules called normally when you include a module). – Peter Gerdes Oct 17 '21 at 18:06
  • 1
    This is not a mixin, it's inheritance – pixelearth Nov 16 '22 at 05:13
  • I'd say the defining characteristic of the mixin is that you can apply it to an arbitrary target where the mixin has no knowledge of that target. This is a type safe mixin that allows constructor logic by using inheritance – Jamie Pate Nov 18 '22 at 03:16
3

You should probably look at Object.assign(). Gotta look something like this:

Object.assign(Test.prototype, mixin);

This will make sure all methods and properties from mixin will be copied into Test constructor's prototype object.

Mister Spock
  • 201
  • 1
  • 6
  • 1
    Is there a way to call that from within the `class` itself? – user779159 Feb 15 '17 at 11:13
  • That is highly doubtful. It is because ES6 classes are just a syntactic sugar hiding good ol' prototypal inheritance. So they are basically prettied-up constructor functions. – Mister Spock Feb 15 '17 at 12:57
  • 1
    related https://stackoverflow.com/questions/46795498/javascript-es6-classes-composition – dcsan May 10 '19 at 19:38
2

I'm surprised to find that none of the answers mentions what I would consider a mixin in the sense of composition (and in contrast to inheritance), which to me is a function that adds functionality to an object. Here's an example making use of both inheritance and composition:

class Pet { constructor(name) { this.name = name } }
class Cat extends Pet { expression = 'miaow' }
class Dog extends Pet { expression = 'bark' }

class Human { constructor(name, age) { this.name = name; this.age = age; } }
class American extends Human { expression = 'say howdy' }

function canSayHello(...contexts) {
  for (const context of contexts) {
    context.sayHello = function() {
     console.log(`Hello my name is ${this.name} and I ${this.expression}`)
    }
  }
}

canSayHello(Pet.prototype, Human.prototype); // apply the mixin

const garfield = new Cat('garfield');
const pluto = new Dog('pluto');
const joebiden = new American('Joe Biden', 79); 

garfield.sayHello();
pluto.sayHello();
joebiden.sayHello();
connexo
  • 53,704
  • 14
  • 91
  • 128
  • 1
    How is this an improvement over `Object.assign(MyClass.prototype, mixin)`? – pixelearth Nov 16 '22 at 05:17
  • In my understanding a mixin is a function that enriches objects/classes/prototypes with functionality. Technically, there's many ways to get there; this is just one of them, that sticks most closely to this understanding. Also, it allows to extend many prototypes in a single call, whereas `Object.assign(MyClass.prototype, mixin)` would require a separate call for each class. – connexo Nov 17 '22 at 07:21