What is the best way(s) to fake function overloading in Javascript?
I know it is not possible to overload functions in Javascript as in other languages. If I neede
This is from a larger body of code which includes the isFn
, isArr
, etc. type checking functions. The VanillaJS version below has been reworked to remove all external dependencies, however you will have to define you're own type checking functions for use in the .add()
calls.
Note: This is a self-executing function (so we can have a closure/closed scope), hence the assignment to window.overload
rather than function overload() {...}
.
window.overload = function () {
"use strict"
var a_fnOverloads = [],
_Object_prototype_toString = Object.prototype.toString
;
function isFn(f) {
return (_Object_prototype_toString.call(f) === '[object Function]');
} //# isFn
function isObj(o) {
return !!(o && o === Object(o));
} //# isObj
function isArr(a) {
return (_Object_prototype_toString.call(a) === '[object Array]');
} //# isArr
function mkArr(a) {
return Array.prototype.slice.call(a);
} //# mkArr
function fnCall(fn, vContext, vArguments) {
//# 0 ? oData[iArgumentCount] || [] : oData);
}; //# overload*.list
//#
a_fnOverloads.push(fnOverload);
registerAlias(fnOverload, oData.default, "default");
return fnOverload;
} //# overload
//#
overload.is = function (fnTarget) {
return (a_fnOverloads.indexOf(fnTarget) > -1);
} //# overload.is
return overload;
}();
The caller defines their overloaded functions by assigning a variable to the return of overload()
. Thanks to chaining, the additional overloads can be defined in series:
var myOverloadedFn = overload(function(){ console.log("default", arguments) })
.add(function(){ console.log("noArgs", arguments) }, [], "noArgs")
.add(function(){ console.log("str", arguments) }, [function(s){ return typeof s === 'string' }], "str")
;
The single optional argument to overload()
defines the "default" function to call if the signature cannot be identified. The arguments to .add()
are:
fn
: function
defining the overload;a_vArgumentTests
: Array
of function
s defining the tests to run on the arguments
. Each function
accepts a single argument and returns true
thy based on if the argument is valid;sAlias
(Optional): string
defining the alias to directly access the overload function (fn
), e.g. myOverloadedFn.noArgs()
will call that function directly, avoiding the dynamic polymorphism tests of the arguments.This implementation actually allows for more than just traditional function overloads as the second a_vArgumentTests
argument to .add()
in practice defines custom types. So, you could gate arguments not only based on type, but on ranges, values or collections of values!
If you look through the 145 lines of code for overload()
you'll see that each signature is categorized by the number of arguments
passed to it. This is done so that we're limiting the number of tests we are running. I also keep track of a call count. With some additional code, the arrays of overloaded functions could be re-sorted so that more commonly called functions are tested first, again adding some measure of performance enhancement.
Now, there are some caveats... As Javascript is loosely typed, you will have to be careful with your vArgumentTests
as an integer
could be validated as a float
, etc.
JSCompress.com version (1114 bytes, 744 bytes g-zipped):
window.overload=function(){'use strict';function b(n){return'[object Function]'===m.call(n)}function c(n){return!!(n&&n===Object(n))}function d(n){return'[object Array]'===m.call(n)}function e(n){return Array.prototype.slice.call(n)}function g(n,p,q){if(q=d(q)?q:e(q),b(n))return n.apply(p||this,q)}function h(n,p,q){q&&!n[q]&&(n[q]=p)}function k(n){var p=b(n)?{default:n}:c(n)?n:{default:function(){throw'Overload not found for arguments: ['+e(arguments)+']'}},q=function(){var r,s,t,u=arguments,v=p[u.length]||[];for(s=0;s