Trying to write a function so that it will handle different numbers and types of argument always seems to take a lot more code that it should. Today I ended up with some nightmarish if...else... because I wanted to support:
- Object, Object
- Object, String
- Object, Object, String
I remembered a post by John Resig from a while back on this, however his method doesn't handle different types. A quick Google found this snippet which does the job but seems a bit clunky, converting constructors to strings and the like. Satisfied that it could probably be improved upon here is my go at it.
Implementation
Here an Impl class holds the different method implementations against their signatures, defined as an array of constructors. The add method maps a new signature to an implementation, the exec method identifies the correct implementation for the current arguments and executes it and the compile method returns a function that can be used to overwrite the "daddy" function.
function Impl(){
var _impl = {};
return {
add: function(aSignature, fImpl){
_impl[aSignature] = fImpl;
},
exec: function(args, scope){
var aArgs = Array.prototype.slice.call(args);
var aCtors = [];
for(var i in aArgs) aCtors.push(aArgs[i].constructor);
return (_impl[aCtors] || function(){}).apply(scope, aArgs);
},
compile: function(){
var impl = this;
return function(){
return impl.exec(arguments, this);
};
}
};
}
Usage
Here's an example setup of a function stringify that will take two strings, two numbers or an array and do different things depending on what arguments are passed.
function stringify(){
var someVar = 'Items: ';
var impl = new Impl();
impl.add([String, String], function(sLeft, sRight){
return sLeft + ': ' + sRight;
});
impl.add([Number, Number], function(iLeft, iRight){
return 'Sum: ' + (iLeft + iRight).toString();
});
impl.add([Array], function(aIn){
return someVar + aIn.join(', ');
});
stringify = impl.compile();
return stringify.apply(this, arguments);
}
Each implementation is added with an array of constructors e.g. [Number, Number] representing the arguments signature to match and the implementation function. Notice that we don't need to do any type checking of the variables in the implementation function, as we have to have matched the signature to get that far, and we can give then relevant names.
Notice the last two lines are:
stringify = impl.compile();
return stringify.apply(this, arguments);
So on the first execution of the function it is overwritten with a "compiled" version and then that version is called on the next line. Subsequent calls to the function will go straight to the "compiled" version thus speeding things up. This could also be done without the compilation and overwriting bit by just calling exec directly thus:
return impl.exec(arguments, this);
Calling our function
stringify('foo', 'bar'); // => 'foo: bar'
stringify(26, 5); // => 'Sum: 31'
stringify( [ 1, 2, 3 ] ); // => 'Items: 1, 2, 3'
Our different numbers and types of argument are giving us the output we'd expect.