Last active
December 23, 2015 11:01
-
-
Save datchley/6625684 to your computer and use it in GitHub Desktop.
Inspired by functional mixins in frameworks like Twitter's Flight, I thought I'd work up a version that handled both functional and object based mixins, and with variable arguments instead of having to pass in an array of mixins to add.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var mixin = function(target /*, mixins */) { | |
var args = [].slice.call(arguments, 1); | |
target.mixins = target.hasOwnProperty('mixins') ? target.mixins : []; | |
args.forEach(function(mixin) { | |
if (target.mixins.indexOf(mixin) === -1) { | |
if (typeof mixin === 'function') { | |
// Functional mixin using 'call' | |
mixin.call(target); | |
} | |
else if (typeof mixin === 'object') { | |
// Simply extend target using object | |
for (prop in mixin) { | |
if (!target.hasOwnProperty(prop)) { | |
target[prop] = mixin[prop]; | |
} | |
} | |
} | |
else { | |
throw new Error("mixin: can't mix an object of type" + typeof mixin); | |
} | |
target.mixins.push(mixin); | |
} | |
}); | |
} | |
// Functional mixin | |
var withHello = function() { | |
this.hello = function() { console.log("Hello!"); } | |
}; | |
var withActions = function() { | |
// Run a function after another function | |
this.after = function(name, fn) { | |
if (typeof this[name] !== 'function') | |
return; | |
var method = this[name]; | |
this[name] = function() { | |
method.apply(this, arguments); | |
fn.apply(this, arguments); | |
} | |
} | |
// Run a function before another function | |
this.before = function(name, fn) { | |
if (typeof this[name] !== 'function') | |
return; | |
var method = this[name]; | |
this[name] = function() { | |
fn.apply(this, arguments); | |
method.apply(this, arguments); | |
} | |
} | |
// Run a function around an existing function, the | |
// existing function is passed to the wrap function | |
// as the first argument | |
this.wrap = function(name, fn) { | |
if (typeof this[name] !== 'function') | |
return; | |
var method = this[name]; | |
this[name] = function() { | |
var args = [].concat(method.bind(this), [].slice.call(arguments)); | |
// pass original method as 1st argument | |
fn.apply(this, args); | |
} | |
} | |
} | |
// A non-function mixin | |
var withSearch = { | |
search: function(what) { | |
what = what || "something"; | |
console.log(this.name + " is searching for " + what); | |
} | |
} | |
// Our base object | |
var Base = function(name) { | |
this.name = name; | |
}; | |
// Create an instance | |
var obj = new Base('Dave'); | |
// Add our first mixin (call twice, as it should only be included once) | |
mixin(obj, withHello); | |
mixin(obj, withHello); | |
obj.hello(); | |
// Verify we have only one mixin | |
console.dir(obj.mixins); | |
// Add in our second mixin, function augmentation | |
mixin(obj, withActions); | |
console.dir(obj.mixins); | |
obj.after('hello', function() { console.log("Nice to meet you. My name is " + this.name + "!"); }); | |
obj.before('hello', function() { console.log("*ahem*"); }); | |
obj.hello(); | |
// Add our non-functional, object mixin (call it twice to ensure it gets added once only) | |
mixin(obj, withSearch); | |
mixin(obj, withSearch); | |
obj.search("a bug"); | |
console.dir(obj.mixins); | |
// Test out our wrap function, wrapping the search method | |
obj.wrap('search', function(wrapfn) { | |
console.log("> *kicks dirt*, looks around."); | |
wrapfn(); | |
console.log("> nope, didn't find it"); | |
}); | |
obj.search("another bug"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment