Перегрузка функций через динамический полиморфизм в 100 строк JS
Это большая часть кода, которая включает в себя функции проверки типа isFn
, isArr
и т. Д. Приведенная ниже версия VanillaJS была переработана для удаления всех внешних зависимостей, однако вам придется определить собственные функции проверки типов для использования в вызовах .add()
.
Примечание: Это самоисполняющаяся функция (поэтому мы можем иметь закрытое / закрытое пространство видимости), следовательно, присваивание window.overload
вместо 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;
}();
Использование:
Вызывающий объект определяет свои перегруженные функции, присваивая переменную для возврата overload()
. Благодаря сцеплению дополнительные перегрузки могут быть определены последовательно:
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")
;
Единственный необязательный аргумент overload()
определяет функцию "по умолчанию", которая вызывается, если подпись не может быть идентифицирована. Аргументы .add()
:
fn
: function
определение перегрузки;
a_vArgumentTests
: Array
из function
s, определяющих тесты для запуска на arguments
. Каждый function
принимает один аргумент и возвращает true
твой в зависимости от того, является ли аргумент действительным;
sAlias
(Необязательно): string
определение псевдонима для прямого доступа к функции перегрузки (fn
), например, myOverloadedFn.noArgs()
будет вызывать эту функцию напрямую, избегая проверки аргументов на динамический полиморфизм.
Эта реализация фактически допускает не только традиционные перегрузки функций, так как второй аргумент a_vArgumentTests
для .add()
на практике определяет пользовательские типы. Таким образом, вы можете задавать аргументы не только по типу, но и по диапазонам, значениям или коллекциям значений!
Если вы посмотрите 145 строк кода для overload()
, вы увидите, что каждая подпись классифицируется по номеру arguments
, переданному ей. Это сделано для того, чтобы мы ограничивали количество тестов, которые мы проводим. Я также отслеживаю количество звонков. С некоторым дополнительным кодом, массивы перегруженных функций могут быть повторно отсортированы, так что сначала тестируются более часто вызываемые функции, снова добавляя некоторую меру повышения производительности.
Теперь, есть некоторые предостережения ... Поскольку Javascript свободно набирается, вы должны быть осторожны с vArgumentTests
, так как integer
может быть проверен как float
и т. Д.
Версия JSCompress.com (1114 байт, 744 байт в формате 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}();