Istnieje wiele aspektów związanych z przeciążaniem argumentów w JavaScript:
Zmienne argumenty - możesz przekazywać różne zestawy argumentów (zarówno typu, jak i ilości), a funkcja będzie zachowywać się w sposób zgodny z przekazanymi do niej argumentami.
Argumenty domyślne - można zdefiniować wartość domyślną argumentu, jeśli nie zostanie ona przekazana.
Nazwane argumenty - kolejność argumentów staje się nieistotna i po prostu określasz, które argumenty chcesz przekazać do funkcji.
Poniżej znajduje się sekcja dotycząca każdej z tych kategorii obsługi argumentów.
Argumenty zmienne
Ponieważ javascript nie ma sprawdzania typu argumentów ani wymaganej liczby argumentów, możesz mieć tylko jedną implementację, myFunc()która dostosuje się do przekazywanych argumentów, sprawdzając typ, obecność lub liczbę argumentów.
jQuery robi to cały czas. Możesz ustawić niektóre argumenty jako opcjonalne lub możesz rozgałęzić swoją funkcję w zależności od tego, jakie argumenty są do niej przekazywane.
Podczas implementowania tego typu przeciążeń można użyć kilku różnych technik:
- Możesz sprawdzić obecność dowolnego argumentu, sprawdzając, czy zadeklarowaną wartością nazwy argumentu jest
undefined.
- Możesz sprawdzić całkowitą ilość lub argumenty za pomocą
arguments.length.
- Możesz sprawdzić typ dowolnego argumentu.
- W przypadku zmiennej liczby argumentów można użyć
argumentspseudo-tablicy, aby uzyskać dostęp do dowolnego argumentu za pomocą arguments[i].
Oto kilka przykładów:
Spójrzmy na obj.data()metodę jQuery . Obsługuje cztery różne formy użytkowania:
obj.data("key");
obj.data("key", value);
obj.data();
obj.data(object);
Każdy z nich wywołuje inne zachowanie i bez użycia tej dynamicznej formy przeciążania wymagałby czterech oddzielnych funkcji.
Oto, jak można rozróżnić wszystkie te opcje w języku angielskim, a następnie połączę je wszystkie w kodzie:
// get the data element associated with a particular key value
obj.data("key");
Jeśli pierwszy przekazany argument .data()jest łańcuchem, a drugi argumentem undefined, to wywołujący musi używać tego formularza.
// set the value associated with a particular key
obj.data("key", value);
Jeśli drugi argument nie jest niezdefiniowany, ustaw wartość określonego klucza.
// get all keys/values
obj.data();
Jeśli nie przekazano żadnych argumentów, zwróć wszystkie klucze / wartości w zwróconym obiekcie.
// set all keys/values from the passed in object
obj.data(object);
Jeśli typ pierwszego argumentu jest zwykłym obiektem, ustaw wszystkie klucze / wartości z tego obiektu.
Oto, jak można połączyć je wszystkie w jeden zestaw logiki javascript:
// method declaration for .data()
data: function(key, value) {
if (arguments.length === 0) {
// .data()
// no args passed, return all keys/values in an object
} else if (typeof key === "string") {
// first arg is a string, look at type of second arg
if (typeof value !== "undefined") {
// .data("key", value)
// set the value for a particular key
} else {
// .data("key")
// retrieve a value for a key
}
} else if (typeof key === "object") {
// .data(object)
// set all key/value pairs from this object
} else {
// unsupported arguments passed
}
},
Kluczem do tej techniki jest upewnienie się, że wszystkie formy argumentów, które chcesz zaakceptować, są jednoznacznie identyfikowalne i nigdy nie ma wątpliwości, której formy używa dzwoniący. Zwykle wymaga to odpowiedniego uporządkowania argumentów i upewnienia się, że typ i pozycja argumentów są na tyle unikalne, aby zawsze można było stwierdzić, która forma jest używana.
Na przykład, jeśli masz funkcję, która przyjmuje trzy argumenty łańcuchowe:
obj.query("firstArg", "secondArg", "thirdArg");
Możesz łatwo ustawić trzeci argument jako opcjonalny i możesz łatwo wykryć ten warunek, ale nie możesz uczynić tylko drugiego argumentu opcjonalnym, ponieważ nie możesz powiedzieć, który z nich ma przekazać wywołujący, ponieważ nie ma sposobu, aby określić, czy drugi argument argument ma być drugim argumentem lub drugi argument został pominięty, więc to, co znajduje się w miejscu drugiego argumentu, jest w rzeczywistości trzecim argumentem:
obj.query("firstArg", "secondArg");
obj.query("firstArg", "thirdArg");
Ponieważ wszystkie trzy argumenty są tego samego typu, nie możesz odróżnić różnych argumentów, więc nie wiesz, co zamierzał dzwoniący. W tym stylu wywoływania tylko trzeci argument może być opcjonalny. Gdybyś chciał pominąć drugi argument, musiałby zostać przekazany jako null(lub inna wykrywalna wartość), a kod wykryłby, że:
obj.query("firstArg", null, "thirdArg");
Oto przykład opcjonalnych argumentów w jQuery. oba argumenty są opcjonalne i przyjmują wartości domyślne, jeśli nie zostaną przekazane:
clone: function( dataAndEvents, deepDataAndEvents ) {
dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
return this.map( function () {
return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
});
},
Oto przykład jQuery, w którym może brakować argumentu lub jeden z trzech różnych typów, co daje cztery różne przeciążenia:
html: function( value ) {
if ( value === undefined ) {
return this[0] && this[0].nodeType === 1 ?
this[0].innerHTML.replace(rinlinejQuery, "") :
null;
// See if we can take a shortcut and just use innerHTML
} else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
(jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
!wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
value = value.replace(rxhtmlTag, "<$1></$2>");
try {
for ( var i = 0, l = this.length; i < l; i++ ) {
// Remove element nodes and prevent memory leaks
if ( this[i].nodeType === 1 ) {
jQuery.cleanData( this[i].getElementsByTagName("*") );
this[i].innerHTML = value;
}
}
// If using innerHTML throws an exception, use the fallback method
} catch(e) {
this.empty().append( value );
}
} else if ( jQuery.isFunction( value ) ) {
this.each(function(i){
var self = jQuery( this );
self.html( value.call(this, i, self.html()) );
});
} else {
this.empty().append( value );
}
return this;
},
Nazwane argumenty
Inne języki (takie jak Python) pozwalają na przekazywanie nazwanych argumentów jako sposobu przekazywania tylko niektórych argumentów i uniezależnienia ich od kolejności, w jakiej są przekazywane. Javascript nie obsługuje bezpośrednio funkcji nazwanych argumentów. Wzorzec projektowy, który jest powszechnie stosowany w jego miejsce, to przekazywanie mapy właściwości / wartości. Można to zrobić, przekazując obiekt z właściwościami i wartościami lub w wersji ES6 i nowszych można faktycznie przekazać sam obiekt Map.
Oto prosty przykład ES5:
jQuery $.ajax()akceptuje formę użycia, w której wystarczy przekazać mu pojedynczy parametr, który jest zwykłym obiektem JavaScript z właściwościami i wartościami. Jakie właściwości mu przekazujesz, określają, które argumenty / opcje są przekazywane do wywołania Ajax. Niektóre mogą być wymagane, wiele jest opcjonalnych. Ponieważ są one właściwościami obiektu, nie ma określonej kolejności. W rzeczywistości istnieje ponad 30 różnych właściwości, które można przekazać do tego obiektu, wymagana jest tylko jedna (adres URL).
Oto przykład:
$.ajax({url: "http://www.example.com/somepath", data: myArgs, dataType: "json"}).then(function(result) {
// process result here
});
Wewnątrz $.ajax()implementacji może po prostu zapytać, które właściwości zostały przekazane do przychodzącego obiektu i użyć ich jako nazwanych argumentów. Można to zrobić za pomocą for (prop in obj)lub przez pobranie wszystkich właściwości do tablicy za pomocą, Object.keys(obj)a następnie iterację tej tablicy.
Ta technika jest bardzo często używana w Javascript, gdy występuje duża liczba argumentów i / lub wiele argumentów jest opcjonalnych. Uwaga: nakłada to obowiązek na funkcję implementującą, aby upewnić się, że obecny jest minimalny prawidłowy zestaw argumentów i przekazać wywołującemu pewne informacje zwrotne dotyczące debugowania, czego brakuje, jeśli przekazano niewystarczające argumenty (prawdopodobnie przez zgłoszenie wyjątku z pomocnym komunikatem o błędzie) .
W środowisku ES6 możliwe jest użycie destrukturyzacji do tworzenia domyślnych właściwości / wartości dla powyższego przekazanego obiektu. Omówiono to bardziej szczegółowo w tym artykule referencyjnym .
Oto przykład z tego artykułu:
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
};
Stwarza to domyślne właściwości i wartości dla start, endi stepwłaściwości na obiekt przekazany do selectEntries()funkcji.
Domyślne wartości argumentów funkcji
W ES6 Javascript dodaje wbudowaną obsługę języka dla domyślnych wartości argumentów.
Na przykład:
function multiply(a, b = 1) {
return a*b;
}
multiply(5); // 5
Dalszy opis sposobów, w jakie można to wykorzystać, znajduje się tutaj na MDN .