JavaScript jest językiem dynamicznym, w którym funkcje mogą przyjmować wiele argumentów. Nie ma w nim jednak mechanizmu, który by wywoływał inne funkcje w zależności do liczby argumentów (czyli nie obsługuje przeciążania funkcji). W tym wpisie przedstawię jak prosto można taki mechanizm dodać do języka.
Idea wzięła się od wpisu na blogu Johna Resiga (tego od jQuery) JavaScript Method Overloading. W swojej implementacji Jonh używał takiego API:
function Users(){
addMethod(this, "find", function(){
// Find all users...
});
addMethod(this, "find", function(name){
// Find a user by name
});
addMethod(this, "find", function(first, last){
// Find a user by first and last name
});
}
var users = new Users();
users.find(); // wyszukaj wszystkie
users.find("John"); // Wysuzkaj po imieniu
users.find("John", "Resig"); // Wyszukaj po imieniu i nazwsiku
users.find("John", "E", "Resig"); // nie zadziała
Poszedłem o krok dalej i napisałem funkcje, której można użyć, przekazując tablicę funkcji. Zwraca ona nową, przeładowaną funkcje. Można jej użyć w ten sposób:
var o = {
x: 10,
foo: method("foo", [
function(a) {
console.log("a: " + a);
},
function(a, b) {
console.log("b: " + a + ' ' + b);
},
function() {
console.log("c: " + this.x);
}
])
};
o.foo(10);
o.foo(10, 20);
o.foo();
try {
o.foo(1,2,3);
} catch (e) {
console.error(e.message);
}
Ostatnie wywołanie zwróci wyjątek ponieważ nie ma funkcji z 3 argumentami.
Moja funkcja method
wygląda tak:
function method(name, fns) {
if (fns instanceof Array) {
if (fns.length == 1) {
return fns[0];
}
// zamiast Array::reduce można zwracać funkcje z pętlą for
return fns.reduce(function(result, fn) {
return function() {
var len = arguments.length;
if (len == fn.length) {
fn.apply(this, arguments);
} else if (typeof result == 'function') {
result.apply(this, arguments);
} else {
throw new Error("Can't find method '" + name + "' with " + len +
' arg' + (len != 1 ? 's' : ''));
}
};
}, null);
} else {
return fns;
}
}
Nazwa method
może nie jest najtrafniejsza, ponieważ zwracana jest zwykła funkcja i lepsza byłaby np. overload
.
Funkcja korzysta z ciekawej właściwości języka JavaScript, gdzie każda funkcja posiada właściwość length
, która
określa liczbę parametrów oraz wewnątrz funkcji arguments.length
, która zawiera liczbę argumentów wywołania.
Można też funkcje uprościć i pobierać listę funkcji jako argumenty, wtedy funkcja wyglądałaby tak:
function method(name) {
var fns = [].slice.call(arguments, 1);
if (fns.length == 1) {
return fns[0];
}
return fns.reduce(function(result, fn) {
return function() {
var len = arguments.length;
if (len == fn.length) {
fn.apply(this, arguments);
} else if (typeof result == 'function') {
result.apply(this, arguments);
} else {
throw new Error("Can't find method '" + name + "' with " + len +
' arg' + (len != 1 ? 's' : ''));
}
};
}, null);
}
Zamiast [].slice.call
można użyć operatora rest z nowej wersji ECMAScript czyli:
function method(name, ...fns) {
// jak wyżej
}
Możesz przetestować funkcje method
w demo na CodePen.
Komentarze
Hasło, które podasz umożliwi ponowne zalogowanie się i np. usunięcie komentarza, jest dobrowolne. Email jest szyfrowany i używany do wysyłania powiadomień o odpowiedziach do komentarzy oraz do pobierania awatara dzięki usłudze Gravatar.com.