OK, OK, probably is just that I cannot wait to start writing stuff with [tinydown](https://github.com/WebReflection/tinydown#tinydown) but I tweeted about this after last #FlightNight and @angustweets demo ... so here what is behind [Flight](http://twitter.github.io/flight/) mixins choice. ### The Basics JavaScript polymorphism is probably one of the best things you can find out there. Not kidding, the flexibility that this language offers when it comes to context injection and runtime class definition is simply amazing, as simple, and amazing, is this idea: ```js // most basic example function enriched() { this.method = function () { // do stuff }; } ``` Most common books and online documents usually describe above example as a _constructor with privileged methods_. Well, that's actually what you get if you `var obj = new enriched();` but that's just one story. Flight uses a different story, offering alternative methods with better semantics but basically going down to: ```js enriched.call(object); ``` ### Mixins Baby! Using above pattern we can enrich any sort of object with the extra benefit of having a private scope the moment we invoke once the function. This gives us the opportunity to pass arguments or simply have some private, shared in case of a prototype, variable too. ```js // with arguments and private variables var Mixin = function Mixin(arg0, argN) { // private variables var scoped = {}; // privileged methods this.method = function method() { return scoped; }; // default properties or initialization this.prop = arg0 || 'default'; this.other = argN; }; // ======================================== // as Singleton var singleton = new Mixin; singleton.method(); // object singleton.prop; // default singleton.other; // undefined // as prototype with defaults function Class() {} Mixin.call(Class.prototype, 'a', 'b'); var instance1 = new Class, instance2 = new Class; instance1.method(); // object instance2.method(); // same object // same methods instance1.method === instance2.method; // true // and same object instance1.method() === instance2.method(); // true instance1.prop; // a instance1.other; // b instance2.prop; // a instance2.other; // b ``` I have realized how powerful is this simply considering the same approach with prototypes rather than each objects, where methods are of course created per each invokation of the mixin, but that would be indeed absolutely meant and expected. What I am saying, is that if you are worried about too many closures and functions generated per each mixin, and you believe your mixin does not need any initialization and methods can be used anywhere without needing a private scope, here all you can do to follow same pattern and save a bit of memory: ```js var Mixin = function(){ // created once and never again function method1(){} function method2(){} function methodN(){} return function () { this.method1 = method1; this.method2 = method2; this.methodN = methodN; }; }(); ``` I guess that's it, right? But what if we need some sort of initialization per each mixin? ### Initializing A Mixing There could be things we need to do to ensure the mixin will work as expected per each instance but if we want to coexist out there in the wild, we cannot name methods like `.init()` because of the name clashing if more than a mixin needs to be initialized ... are you following? One possible solution that came into my mind is to prefix, with `init` the method, and use the mixin name as suffix. ```js // defaults plus init var Mixin = function Mixin(dflt0, dfltN) { // private variables var scoped = {}; // privileged methods this.method = function method() { return scoped; }; // avoid multiple mixins name clashing // prefix eventual initialization // with 'init' plus mixinname this.initMixin = function (arg0, argN) { this.prop = arg0 || dflt0 || 'default'; this.other = argN || dfltN; }; }; ``` If we follow this approach, we can initialize any mixin we want at any time, e.g.: ```js // explicit initialization function Class(arg0, argN) { this.initMixin(arg0, argN); } Mixin.call(Class.prototype, 'a', 'b'); var instance1 = new Class, instance2 = new Class('different'); instance1.prop; // a instance2.prop; // different instance1.other; // b instance2.other; // b ``` Per each instance we can then enrich the constructor so that every mixin that needs to be initialized at that time can be simply call the non clashing method. #### Some Bad Idea You might Have Well, it is common in JS to think about `for/in` loops and stuff so that if we know a prefix we can automate tasks ... but in this case I strongly discourage this approach unless you are not 100% sure all mixins initializations support same signature. ```js // *DONT* automatic mixin initialization function Class() { for (var key in this) { if (key.slice(0, 4) === 'init') { this[key](); // or this[key].apply(this, arguments); } } } ``` In few words, using above technique means coupling mixins with a specific, probably unknown in advance, class signature so it is strongly discouraged. Sticking with an explicit mixin initialization is not only faster but able to create any sort of mixin signature. ### Events To The Rescue As @kpk reminds me, [Flight is event based](https://twitter.com/kpk/status/323272363053551616) so there's also an easy way to do more after all mixins initialization: `this.after('initialize', fn);` ### As Summary This is only one part of [Flight](http://twitter.github.io/flight/) where [**performance**](http://jsperf.com/mixin-fun/31) were strongly considered for all old and new browsers and this works. In an era were many developers are looking for better classes this framework came out with such simple and powerful solution I can't believe I would like to have anything more than this for real apps! Enjoy the Flight philosophy and embrace powerful simplicity any time you _A-HA_ it ;)
behind the design
My JavaScript book is out!
Don't miss the opportunity to upgrade your beginner or average dev skills.
Sunday, April 14, 2013
Flight Mixins Are Awesome!
Subscribe to:
Post Comments (Atom)
3 comments:
I have been using this method for mixins for a very long while (eg https://github.com/arieh/Events), and it's amazing!
I think the main advantage for this method is that it makes it possible to apply any mixin to any object without a use of a 3rd party tool.
problem is `Object.mixin` in ES6 that will not accept functions in this way I guess ... you have two options at that time:
1. check the function prototype and mixin that
2. invoke the function with current object
I would personally check that the prototype has no own properties and in that case invoke it instead of going through the prototype check ... oh well, this requires a gist :-)
I think you'd like YUI's augment() function. See http://yuilibrary.com/yui/docs/api/classes/YUI.html#method_augment
Post a Comment