Перевантаження функцій за допомогою динамічного поліморфізму в 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
й визначають тестів для запуску на arguments
. Кожен function
приймає єдиний аргумент і повертає true
ваше, виходячи з того, якщо аргумент дійсний;
sAlias
(Необов’язково): string
визначаючи псевдонім для прямого доступу до функції перевантаження ( fn
), наприклад, myOverloadedFn.noArgs()
буде викликати цю функцію безпосередньо, уникаючи динамічних тестів на поліморфізм аргументів.
Ця реалізація фактично дозволяє не просто традиційні перевантаження функцій, оскільки другий a_vArgumentTests
аргумент на .add()
практиці визначає власні типи. Отже, ви можете брати аргументи не лише на основі типу, але і на діапазони, значення або колекції значень!
Якщо ви переглянете 145 рядків коду для overload()
то побачите, що кожен підпис класифікується за кількістю arguments
переданих до нього. Це робиться для того, щоб ми обмежили кількість проведених тестів. Я також стежу за кількістю дзвінків. З деяким додатковим кодом масиви перевантажених функцій можуть бути пересортировані таким чином, щоб спочатку випробовувалися частіше викликані функції, знову додаючи певний показник підвищення продуктивності.
Зараз є деякі застереження ... Оскільки Javascript буде набрано слабко, вам доведеться бути обережними зі своїм vArgumentTests
оскільки це integer
може бути перевірено як і float
т.д.
Версія JSCompress.com (1114 байт, 744 байтів 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<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}();