justinfagnani / mixwith.js

A mixin library for ES6

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ES3 Implementation

audinue opened this issue · comments

IE6 compatible. Just for fun. Nothing serious. Tell me what you think about this 😄

function Mixin1 (superClass) {
  function Mixin1 () {
    // Passing constructor arguments
    superClass.apply(this, arguments)
  }
  Mixin1.prototype = new superClass()
  // Calling super class method
  Mixin1.prototype.callSuperClassMethodExample = function () {
    superClass.prototype.superClassMethod()
    return this
  }
  return Mixin1
}

function Mixin2 (superClass) {
  function Mixin2 () {
    superClass.apply(this, arguments)
  }
  Mixin2.prototype = new superClass()
  Mixin2.prototype.anotherMixinMethod = function () {
    alert('Hello from mixin!')
  }
  return Mixin2
}

// Composing mixins
function Mixin3 (superClass) {
  return Mixin2(Mixin1(superClass))
}

// Test it
var Foo = (function () {
  function Foo () {
  }
  Foo.prototype.superClassMethod = function () {
    alert('Hello from superClass!')
  }
  return Mixin3(Foo)
}())

new Foo().callSuperClassMethodExample().anotherMixinMethod()

Mixin1.prototype = new superClass()

I don't think you should use new there, since that might run logic meant for the instance when the instance is created, not when the class is defined. I think you can use Object.create (polyfill), if that works in ES3?

Mixin1.prototype = Object.create(superClass.prototype)

That's right.

I was thinking about this a little more. The real reason to not instantiate the super class constructor automatically is because this prevents the extending developer from being able to control when the super constructor is called in the new subclass. They may assume that the constructor has not already been called and may call the constructor a second time in their extending class, which could possibly cause bugs.

But, if you're calling new Something as part of the implementation of how your mixin tool works, and placing that somewhere in the prototype chain, then that's fine, but the super class should be left alone because the super class is being passed in by the developer using the mixin tool and defining a class that extends the super class (with mixins), so we want to let the developer have control over how the subclass and superclass are designed to work without the mixin tool doing unexpected things behind the scenes.

I like this :)

As opposed to new superclass(), you can do superclass.call(this).

Babel produces code that's basically similar:

var M = function M(superclass) {
  return function (_superclass) {
    _inherits(_class, _superclass);

    function _class() {
      _classCallCheck(this, _class);

      return _possibleConstructorReturn(this, _superclass.apply(this, arguments));
    }

    _class.prototype.foo = function foo() {
      return 'foo';
    };

    return _class;
  }(superclass);
};

I'm going to close this and soon figure out whether a version should be distributed that's compiled to ES5.

Love this pattern!

Did you end up making this ES5-compiled implementation? I'm writing a mixin for an ES5 library and it'd be really useful. Wondering if I should roll my own.

Just thought I'd share, here's how I make my mixin classes: https://github.com/trusktr/infamous/blob/master/src/motor/Observable.js

In particular, note that the exported class can be used as a mixin, or as a normal class, and also note what I'm doing with Symbol.hasInstance which is what makes instanceof work regardless of which way you use the class/mixin.

You'd use it like this:

import Observable from 'infamous/motor/Observable'

// extend directly like a normal class
class Foo extends Observable { ... }

console.log(Foo instanceof Observable) // true

or

import Observable from 'infamous/motor/Observable'

class Foo { ... }

// or use it as a mixin
class Bar extends Observable.mixin(Foo) { ... }

console.log(Foo instanceof Observable) // true

@paulmelnikow I just compile mine to ES5 with Babel currently. You could also try Bublé, which is much more lightweight and produces hand-written-like output, with some limitations. If you can stay within limitations, Buble might be an easy way to write your using class and transpile it for ES5.

Thanks for that. Interesting ideas.

What I'm looking for is more of a backport of the pattern to ES5. The idea is to adhere to the pattern as much as possible, so that it is easy to move to this pattern and library once the base library has switched to classes. I'd rather not introduce a transpiler at this stage, since it makes things more complicated than necessary.

@paulmelnikow

I'd rather not introduce a transpiler at this stage, since it makes things more complicated than necessary.

I agree, which is why I mentioned Bublé, it really outputs really really non-complicated code. It will give you code that is very similar to what you'll write by hand, literally.

To get started by hand though, you can easily write class-factory mixin for ES5, f.e. a starting point:

function FooMixin(base) {
  if (typeof base != 'function') throw new Error('not function')

  // class Foo
  function Foo() {
    // constructor logic for Foo class
  }

  // extends base
  Foo.prototype = Object.create(base.prototype)

  Foo.prototype.someMethod = function() { ... }

  return Foo

  // implement Symbol.hasInstane-like stuff here.
}

@paulmelnikow Here's my Observable class transpiled with Buble, it is almost the same code.

It just leaves Symbol.hasInstance in place, but I don't know if it will work in older browsers that don't read the Symbol.hasInstance property on constructor when using instanceof.

That's a good snippet. Looks similar to what I have, except:

function IcedFrisbyNock () {
  superClass.apply(this, arguments);
}

I also want to transfer statics:

// Transfer statics.
Object.assign(FooMixin, superClass);

I haven't tested that in all cases (the case I'm using is more complicated), though I think it should work as long as it comes before the assignment of prototype.

Bublé is cool to know about. I should have been clearer, though. I mean that I don't want a complicated toolchain.

superClass.apply(this, arguments);
// Transfer statics.

Oh yep, forgot about those. I think you meant Object.assign(IcedFrisbyNock, superClass);, but yeah, good point, ES6 classes do that automatically too.