Funktionsüberladung durch dynamischen Polymorphismus in 100 Zeilen JS
Dies wird von einem größeren Körper von Code, der beinhaltet isFn
, isArr
usw. Typen Prüffunktionen. Die folgende VanillaJS-Version wurde überarbeitet, um alle externen Abhängigkeiten zu entfernen. Sie müssen jedoch Ihre eigenen Typprüfungsfunktionen für die Verwendung in den .add()
Aufrufen definieren.
Hinweis: Dies ist eine selbstausführende Funktion (so dass wir einen geschlossenen / geschlossenen Bereich haben können), daher die Zuordnung zu window.overload
und nicht 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) {
//# <ES5 Support for array-like objects
//# See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Browser_compatibility
vArguments = (isArr(vArguments) ? vArguments : mkArr(vArguments));
if (isFn(fn)) {
return fn.apply(vContext || this, vArguments);
}
} //# fnCall
//#
function registerAlias(fnOverload, fn, sAlias) {
//#
if (sAlias && !fnOverload[sAlias]) {
fnOverload[sAlias] = fn;
}
} //# registerAlias
//#
function overload(vOptions) {
var oData = (isFn(vOptions) ?
{ default: vOptions } :
(isObj(vOptions) ?
vOptions :
{
default: function (/*arguments*/) {
throw "Overload not found for arguments: [" + mkArr(arguments) + "]";
}
}
)
),
fnOverload = function (/*arguments*/) {
var oEntry, i, j,
a = arguments,
oArgumentTests = oData[a.length] || []
;
//# Traverse the oArgumentTests for the number of passed a(rguments), defaulting the oEntry at the beginning of each loop
for (i = 0; i < oArgumentTests.length; i++) {
oEntry = oArgumentTests[i];
//# Traverse the passed a(rguments), if a .test for the current oArgumentTests fails, reset oEntry and fall from the a(rgument)s loop
for (j = 0; j < a.length; j++) {
if (!oArgumentTests[i].tests[j](a[j])) {
oEntry = undefined;
break;
}
}
//# If all of the a(rgument)s passed the .tests we found our oEntry, so break from the oArgumentTests loop
if (oEntry) {
break;
}
}
//# If we found our oEntry above, .fn.call its .fn
if (oEntry) {
oEntry.calls++;
return fnCall(oEntry.fn, this, a);
}
//# Else we were unable to find a matching oArgumentTests oEntry, so .fn.call our .default
else {
return fnCall(oData.default, this, a);
}
} //# fnOverload
;
//#
fnOverload.add = function (fn, a_vArgumentTests, sAlias) {
var i,
bValid = isFn(fn),
iLen = (isArr(a_vArgumentTests) ? a_vArgumentTests.length : 0)
;
//#
if (bValid) {
//# Traverse the a_vArgumentTests, processinge each to ensure they are functions (or references to )
for (i = 0; i < iLen; i++) {
if (!isFn(a_vArgumentTests[i])) {
bValid = _false;
}
}
}
//# If the a_vArgumentTests are bValid, set the info into oData under the a_vArgumentTests's iLen
if (bValid) {
oData[iLen] = oData[iLen] || [];
oData[iLen].push({
fn: fn,
tests: a_vArgumentTests,
calls: 0
});
//#
registerAlias(fnOverload, fn, sAlias);
return fnOverload;
}
//# Else one of the passed arguments was not bValid, so throw the error
else {
throw "poly.overload: All tests must be functions or strings referencing `is.*`.";
}
}; //# overload*.add
//#
fnOverload.list = function (iArgumentCount) {
return (arguments.length > 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;
}();
Verwendungszweck:
Der Aufrufer definiert seine überladenen Funktionen, indem er der Rückgabe von eine Variable zuweist overload()
. Dank der Verkettung können die zusätzlichen Überlastungen in Reihe definiert werden:
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")
;
Das einzige optionale Argument zum overload()
Definieren der "Standard" -Funktion, die aufgerufen werden soll, wenn die Signatur nicht identifiziert werden kann. Die Argumente .add()
sind:
fn
: function
Definieren der Überlast;
a_vArgumentTests
: Array
of function
s definiert die Tests, die auf dem ausgeführt werden sollen arguments
. Jeder function
akzeptiert ein einzelnes Argument und gibt true
Ihr Argument zurück, basierend darauf, ob das Argument gültig ist.
sAlias
(Optional): string
Wenn Sie den Alias für den direkten Zugriff auf die Überladungsfunktion ( fn
) definieren, myOverloadedFn.noArgs()
wird diese Funktion z. B. direkt aufgerufen, wobei die dynamischen Polymorphismustests der Argumente vermieden werden.
Diese Implementierung ermöglicht tatsächlich mehr als nur herkömmliche Funktionsüberladungen, da das zweite a_vArgumentTests
Argument .add()
in der Praxis benutzerdefinierte Typen definiert. Sie können also Argumente nicht nur anhand des Typs, sondern auch anhand von Bereichen, Werten oder Wertesammlungen steuern!
Wenn Sie die 145 Codezeilen overload()
durchsehen, werden Sie feststellen, dass jede Signatur nach der Anzahl der arguments
übergebenen Signaturen kategorisiert ist . Dies geschieht, damit wir die Anzahl der durchgeführten Tests begrenzen. Ich verfolge auch die Anzahl der Anrufe. Mit etwas zusätzlichem Code könnten die Arrays überladener Funktionen neu sortiert werden, sodass häufig aufgerufene Funktionen zuerst getestet werden, wodurch wiederum ein gewisses Maß an Leistungssteigerung hinzugefügt wird.
Nun, es gibt einige Einschränkungen ... Da Javascript lose geschrieben ist, müssen Sie vorsichtig mit Ihrem sein, vArgumentTests
da integer
es als float
usw. validiert werden könnte .
JSCompress.com-Version (1114 Byte, 744 Byte mit G-ZIP):
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<v.length;s++){for(r=v[s],t=0;t<u.length;t++)if(!v[s].tests[t](u[t])){r=void 0;break}if(r)break}return r?(r.calls++,g(r.fn,this,u)):g(p.default,this,u)};return q.add=function(r,s,t){var u,v=b(r),w=d(s)?s.length:0;if(v)for(u=0;u<w;u++)b(s[u])||(v=_false);if(v)return p[w]=p[w]||[],p[w].push({fn:r,tests:s,calls:0}),h(q,r,t),q;throw'poly.overload: All tests must be functions or strings referencing `is.*`.'},q.list=function(r){return 0<arguments.length?p[r]||[]:p},l.push(q),h(q,p.default,'default'),q}var l=[],m=Object.prototype.toString;return k.is=function(n){return-1<l.indexOf(n)},k}();