Przeciążenie funkcji przez dynamiczny polimorfizm w 100 liniach JS
Jest to z większego ciała kodu, który obejmuje isFn
, isArr
itd funkcji Kontrola typów. Poniższa wersja VanillaJS została przerobiona w celu usunięcia wszystkich zewnętrznych zależności, jednak będziesz musiał zdefiniować własne funkcje sprawdzania typu do użycia w .add()
wywołaniach.
Uwaga: Jest to funkcja samowykonująca się (dzięki czemu możemy mieć zakres zamknięcia / zamknięcia), stąd przypisanie do window.overload
zamiast 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;
}();
Stosowanie:
Program wywołujący określa ich przeciążone funkcje, przypisując zmienną do zwracanej wartości overload()
. Dzięki łączeniu dodatkowe przeciążenia można zdefiniować szeregowo:
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")
;
Pojedynczy opcjonalny argument overload()
definiujący funkcję „domyślną”, która ma być wywoływana, jeśli podpis nie może zostać zidentyfikowany. Argumenty do .add()
:
fn
: function
określenie przeciążenia;
a_vArgumentTests
: Array
z function
s definiuje testy do uruchomienia na arguments
. Każdy function
akceptuje pojedynczy argument i zwraca true
twój na podstawie tego, czy argument jest poprawny;
sAlias
(Opcjonalnie): string
zdefiniowanie aliasu w celu uzyskania bezpośredniego dostępu do funkcji przeciążenia ( fn
), np. myOverloadedFn.noArgs()
Wywoła tę funkcję bezpośrednio, unikając dynamicznych testów polimorfizmu argumentów.
Ta implementacja faktycznie pozwala na więcej niż tylko tradycyjne przeciążenie funkcji, ponieważ drugi a_vArgumentTests
argument .add()
w praktyce definiuje typy niestandardowe. Możesz więc przekazywać argumenty nie tylko na podstawie typu, ale także na zakresach, wartościach lub kolekcjach wartości!
Jeśli przejrzysz 145 linii kodu overload()
, zobaczysz, że każdy podpis jest podzielony na kategorie według liczby arguments
przekazanych do niego znaków. Robimy to, aby ograniczyć liczbę uruchomionych testów. Śledzę również liczbę połączeń. Przy pewnym dodatkowym kodzie tablice przeciążonych funkcji mogą zostać ponownie posortowane, aby najpierw przetestować częściej nazywane funkcje, ponownie dodając pewną miarę zwiększenia wydajności.
Teraz są pewne zastrzeżenia ... Ponieważ Javascript jest luźno wpisany, będziesz musiał uważać na swój, vArgumentTests
ponieważ integer
można go zweryfikować jako float
itp.
Wersja JSCompress.com (1114 bajtów, 744 bajty skompresowane g):
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}();