Jak najlepiej ustalić, czy argument nie jest wysyłany do funkcji JavaScript

236

Widziałem teraz 2 metody określania, czy argument został przekazany do funkcji JavaScript. Zastanawiam się, czy jedna metoda jest lepsza od drugiej, czy też jest po prostu nieodpowiednia w użyciu?

 function Test(argument1, argument2) {
      if (Test.arguments.length == 1) argument2 = 'blah';

      alert(argument2);
 }

 Test('test');

Lub

 function Test(argument1, argument2) {
      argument2 = argument2 || 'blah';

      alert(argument2);
 }

 Test('test');

O ile mogę stwierdzić, oba skutkują tym samym, ale pierwszego użyłem tylko w produkcji.

Kolejna opcja, o której wspomniał Tom :

function Test(argument1, argument2) {
    if(argument2 === null) {
        argument2 = 'blah';
    }

    alert(argument2);
}

Zgodnie z komentarzem Juana lepiej byłoby zmienić sugestię Toma na:

function Test(argument1, argument2) {
    if(argument2 === undefined) {
        argument2 = 'blah';
    }

    alert(argument2);
}
Darryl Hein
źródło
To jest naprawdę to samo. Jeśli zawsze masz statyczną liczbę argumentów, skorzystaj z drugiej metody, w przeciwnym razie prawdopodobnie łatwiej będzie iterować za pomocą tablicy argumentów.
Luca Matteis
7
Argument, który nie został przekazany, jest nieokreślony. Testowanie przy ścisłej równości z wartością zerową zakończy się niepowodzeniem. Powinieneś stosować ścisłą równość z nieokreślonym.
Juan Mendes,
19
Pamiętaj: argument2 || 'blah';spowoduje „bla”, jeśli argument2jest false(!), A nie tylko, jeśli jest niezdefiniowane. Jeśli argument2jest wartością logiczną, a funkcja jest falsedla niej przekazywana , linia ta zwróci „bla”, mimo że argument2jest poprawnie zdefiniowana .
Sandy Gifford,
9
@SandyGifford: Same problem, jeśli argument2jest 0, ''albo null.
rvighne
@rvighne Very true. Unikalna interpretacja JavaScript obiektów i rzutowania jest jednocześnie najlepszymi i najgorszymi częściami.
Sandy Gifford

Odpowiedzi:

274

Istnieje kilka różnych sposobów sprawdzenia, czy argument został przekazany do funkcji. Oprócz dwóch wymienionych w (oryginalnym) pytaniu - sprawdzanie arguments.lengthlub używanie ||operatora do podania wartości domyślnych - można również jawnie sprawdzić argumenty za undefinedpośrednictwem argument2 === undefinedlub typeof argument2 === 'undefined'jeśli jeden jest paranoiczny (patrz komentarze).

Korzystanie z ||operatorem stał się standardową praktyką - wszystkie fajne dzieciaki to zrobić - ale bądź ostrożny: Wartość domyślna zostanie wyzwolony jeśli analizuje schemat argument false, czyli może to być rzeczywiście undefined, null, false, 0, ''(lub czegokolwiek innego, dla którego Boolean(...)powraca false).

Pytanie brzmi, kiedy należy użyć, które sprawdzanie, ponieważ wszystkie dają nieco inne wyniki.

Sprawdzanie arguments.lengthwykazuje zachowanie „najbardziej poprawne”, ale może nie być wykonalne, jeśli istnieje więcej niż jeden opcjonalny argument.

Test na undefinednastępny jest „najlepszy” - „kończy się niepowodzeniem” tylko wtedy, gdy funkcja jest jawnie wywoływana z undefinedwartością, która najprawdopodobniej powinna być traktowana tak samo, jak pominięcie argumentu.

Użycie ||operatora może spowodować użycie wartości domyślnej, nawet jeśli podany zostanie prawidłowy argument. Z drugiej strony jego zachowanie może być pożądane.

Podsumowując: używaj go tylko wtedy, gdy wiesz, co robisz!

Moim zdaniem użycie ||jest również dobrym rozwiązaniem, jeśli istnieje więcej niż jeden opcjonalny argument i nie chcemy przekazać literału obiektu jako obejścia nazwanych parametrów.

Inny fajny sposób na arguments.lengthpodanie wartości domyślnych jest możliwy poprzez przejrzenie etykiet instrukcji switch:

function test(requiredArg, optionalArg1, optionalArg2, optionalArg3) {
    switch(arguments.length) {
        case 1: optionalArg1 = 'default1';
        case 2: optionalArg2 = 'default2';
        case 3: optionalArg3 = 'default3';
        case 4: break;
        default: throw new Error('illegal argument count')
    }
    // do stuff
}

Ma to tę wadę, że intencja programisty nie jest (wizualnie) oczywista i używa „magicznych liczb”; dlatego może być podatny na błędy.

Christoph
źródło
42
Powinieneś faktycznie sprawdzić typeof argument2 === „undefined”, na wypadek, gdyby ktoś zdefiniował „undefined”.
JW.
133
Dodam zawiadomienie - ale jacy chorzy dranie robią takie rzeczy?
Christoph
12
undefined jest zmienną w przestrzeni globalnej. Wolniej jest wyszukiwać tę zmienną w zakresie globalnym niż zmienną w zakresie lokalnym. Ale najszybszym jest użycie typeof x === „undefined”
jakieś
5
Ciekawy. Z drugiej strony, niezdefiniowane porównanie === może być szybsze niż porównanie ciągu. Moje testy wydają się wskazywać, że masz rację: x === undefined potrzebował ~ 1,5 razy więcej niż typ x === „undefined”
Christoph
4
@Cristoph: po przeczytaniu twojego komentarza zapytałem. Udało mi się udowodnić, że porównanie łańcuchów z pewnością nie jest (tylko) przez porównanie wskaźników, ponieważ porównywanie gigantycznego ciągu zajmuje więcej niż mały ciąg. Jednak porównywanie dwóch gigantycznych ciągów jest naprawdę powolne tylko wtedy, gdy zostały zbudowane z konkatenacji, ale bardzo szybko, jeśli drugi został utworzony jako zadanie od pierwszego. Więc to chyba działa poprzez badanie wskaźnika następuje litera po literze testujący tak, tak, byłem pełen gówna :) dzięki za oświecenie mnie ...
Juan Mendes
17

Jeśli używasz jQuery, jedną z przyjemnych opcji (szczególnie w skomplikowanych sytuacjach) jest użycie metody rozszerzającej jQuery .

function foo(options) {

    default_options = {
        timeout : 1000,
        callback : function(){},
        some_number : 50,
        some_text : "hello world"
    };

    options = $.extend({}, default_options, options);
}

Jeśli wywołasz funkcję, to w ten sposób:

foo({timeout : 500});

Zmienna opcji wyglądałaby wtedy:

{
    timeout : 500,
    callback : function(){},
    some_number : 50,
    some_text : "hello world"
};
Greg Tatum
źródło
15

To jeden z niewielu przypadków, w których znajduję test:

if(! argument2) {  

}

działa całkiem nieźle i niesie prawidłową implikację składniową.

(Przy jednoczesnym ograniczeniu, że nie pozwolę na uzasadnioną wartość zerową, dla argument2której ma inne znaczenie; ale byłoby to naprawdę mylące).

EDYTOWAĆ:

To jest naprawdę dobry przykład różnicy stylistycznej między językami luźno i mocno pisanymi; oraz opcja stylistyczna, którą zapewnia javascript w pikach.

Moje osobiste preferencje (bez krytyki w stosunku do innych preferencji) to minimalizm. Im mniej kod ma do powiedzenia, dopóki jestem spójny i zwięzły, tym mniej ktoś inny musi zrozumieć, aby poprawnie wywnioskować moje znaczenie.

Jedną z implikacji tej preferencji jest to, że nie chcę - nie uważam za użyteczne - układania wielu testów zależności typu. Zamiast tego staram się, aby kod oznaczał to, jak wygląda; i testuj tylko pod kątem tego, co naprawdę będę musiał przetestować.

Jednym z zaostrzeń, które dostrzegam w kodzie innych ludzi, jest potrzeba ustalenia, czy w szerszym kontekście oczekują, czy rzeczywiście trafią na przypadki, w których testują. Lub jeśli próbują przetestować wszystko, co możliwe, na szansę, że nie przewidują wystarczająco kontekstu. Co oznacza, że ​​w końcu muszę je dokładnie wyśledzić w obu kierunkach, zanim będę mógł cokolwiek zmienić lub zmodyfikować. Myślę, że istnieje spora szansa, że ​​mogliby przeprowadzić te różne testy, ponieważ przewidzieli okoliczności, w których będą potrzebne (i które zwykle nie są dla mnie oczywiste).

(Uważam, że to poważna wada w sposobie, w jaki ci ludzie używają dynamicznych języków. Zbyt często ludzie nie chcą rezygnować ze wszystkich testów statycznych i kończą je udawanie).

Widziałem to najbardziej rażąco w porównaniu obszernego kodu ActionScript 3 z eleganckim kodem javascript. AS3 może być 3 lub 4 razy większy od js, a podejrzewam, że niezawodność przynajmniej nie jest lepsza, tylko z powodu liczby (3-4X) podjętych decyzji w sprawie kodowania.

Jak mówisz, Shog9, YMMV. :RE

dkretz
źródło
1
if (! argument2) argument2 = „default” to argument argument2 = argument2 || „default” - druga wersja jest dla mnie przyjemniejsza wizualnie…
Christoph
5
I uważam to za bardziej pełne i rozpraszające; ale jestem pewien, że to osobiste preferencje.
dkretz
4
@le dorfier: wyklucza również użycie pustych ciągów, 0 i boolean false.
Shog9
@le dorfier: poza estetyką istnieje jedna kluczowa różnica: ta ostatnia skutecznie tworzy drugą ścieżkę wykonania, która może kusić nieostrożnych opiekunów, aby dodawali zachowania wykraczające poza proste przypisanie wartości domyślnej. Oczywiście YMMV.
Shog9
3
co jeśli parametr2 jest wartością logiczną === false; lub funkcja {return false;}?
fresko
7

Istnieją znaczne różnice. Ustawmy kilka przypadków testowych:

var unused; // value will be undefined
Test("test1", "some value");
Test("test2");
Test("test3", unused);
Test("test4", null);
Test("test5", 0);
Test("test6", "");

Przy pierwszej opisanej metodzie tylko drugi test użyje wartości domyślnej. Druga metoda będzie domyślnie wszystko ale pierwszy (jak JS przekonwertuje undefined, null, 0, i ""do wartości logicznej false. A jeśli było użyć metody Toma, tylko czwarta próba użyje domyślnego!

Wybór metody zależy od zamierzonego zachowania. Jeśli wartości inne niż undefinedsą dozwolone argument2, prawdopodobnie będziesz potrzebować pewnej odmiany pierwszego; jeśli pożądana jest niezerowa, niepusta, niepusta wartość, wówczas druga metoda jest idealna - w rzeczywistości często stosuje się ją do szybkiego wyeliminowania tak szerokiego zakresu wartości z rozważań.

Shog9
źródło
7
url = url === undefined ? location.href : url;
Lamy
źródło
Gołe kości odpowiadają. Niektóre wyjaśnienia nie zaszkodziłyby.
Paul Rooney,
Jest to potrójny operator, który zwięźle mówi: Jeśli adres URL jest niezdefiniowany (brak), ustaw zmienną url na location.href (bieżąca strona internetowa), w przeciwnym razie ustaw zmienną url na zdefiniowany adres URL.
rmooney
5

W ES6 (ES2015) możesz użyć parametrów domyślnych

function Test(arg1 = 'Hello', arg2 = 'World!'){
  alert(arg1 + ' ' +arg2);
}

Test('Hello', 'World!'); // Hello World!
Test('Hello'); // Hello World!
Test(); // Hello World!

Andriy2
źródło
Ta odpowiedź jest naprawdę interesująca i może być przydatna w op. Chociaż tak naprawdę nie odpowiada na pytanie
Ulysse BN
Jak widzę - chce zdefiniować arg, jeśli nie został zdefiniowany, więc zamieściłem przydatne informacje
Andriy2,
Chodzi mi o to, że nie odpowiadasz na pytanie, jak najlepiej ustalić, czy argument nie jest wysyłany do funkcji JavaScript . Ale na pytanie można odpowiedzieć przy użyciu domyślnych argumentów: na przykład nazywanie cię argumentami "default value"i sprawdzanie, czy wartość jest rzeczywiście "default value".
Ulysse BN,
4

Przepraszam, wciąż nie mogę komentować, więc aby odpowiedzieć na odpowiedź Toma ... W javascript (undefined! = Null) == false W rzeczywistości ta funkcja nie będzie działać z „null”, powinieneś użyć „undefined”

Luca Matteis
źródło
1
I Tom otrzymał dwie głosy poparcia za złą odpowiedź - zawsze miło jest wiedzieć, jak dobrze działają te systemy społeczności;)
Christoph
3

Dlaczego nie skorzystać z !!operatora? Ten operator, umieszczony przed zmienną, zamienia ją na wartość logiczną (o ile dobrze rozumiem), więc !!undefinedi !!null(a nawet !!NaN, co może być całkiem interesujące) powróci false.

Oto przykład:

function foo(bar){
    console.log(!!bar);
}

foo("hey") //=> will log true

foo() //=> will log false
RallionRl
źródło
Nie działa z logicznym ciągiem true, zero i pustym ciągiem logicznym. Na przykład foo (0); będzie logował się fałszywie, ale foo (1) będzie logował się prawda
rosell.dk 18.08.16
2

Podejście do wykrywania argumentów może być wygodne, wywołując funkcję za pomocą obiektu właściwości opcjonalnych:

function foo(options) {
    var config = { // defaults
        list: 'string value',
        of: [a, b, c],
        optional: {x: y},
        objects: function(param){
           // do stuff here
        }
    }; 
    if(options !== undefined){
        for (i in config) {
            if (config.hasOwnProperty(i)){
                if (options[i] !== undefined) { config[i] = options[i]; }
            }
        }
    }
}
1nfiniti
źródło
2

Istnieje również trudny sposób stwierdzenia, czy parametr jest przekazywany do funkcji, czy nie . Spójrz na poniższy przykład:

this.setCurrent = function(value) {
  this.current = value || 0;
};

Oznacza to, że jeśli wartość parametru valuenie występuje / nie została przekazana - ustaw ją na 0.

Całkiem fajne, huh!

Mohammed Zameer
źródło
1
To tak naprawdę oznacza „jeśli valuejest równoważne false, ustaw na 0”. Jest to subtelne, ale bardzo ważne rozróżnienie.
Charles Wood,
@CharlesWood Wartość nieprzekazana / obecna oznacza tylko fałsz
Mohammed Zameer
1
Jasne, jeśli pasuje to do twojej funkcji. Ale jeśli, na przykład, twój parametr ma wartość logiczną, to truei falsesą poprawnymi wartościami, i możesz chcieć mieć trzecie zachowanie, gdy parametr nie zostanie w ogóle przekazany (szczególnie jeśli funkcja ma więcej niż jeden parametr).
Charles Wood
1
I powinienem przyznać, że jest to ogromny argument w informatyce i może po prostu być kwestią opinii: D
Charles Wood
@CharlesWood przepraszam za spóźnienie na imprezę. Sugeruję, aby dodać je w samej odpowiedzi z opcją edycji
Mohammed Zameer
1

Czasami możesz również chcieć sprawdzić typ, szczególnie jeśli używasz funkcji jako getter i setter. Następujący kod to ES6 (nie będzie działał w EcmaScript 5 lub starszym):

class PrivateTest {
    constructor(aNumber) {
        let _aNumber = aNumber;

        //Privileged setter/getter with access to private _number:
        this.aNumber = function(value) {
            if (value !== undefined && (typeof value === typeof _aNumber)) {
                _aNumber = value;
            }
            else {
                return _aNumber;
            }
        }
    }
}
nbloqs
źródło
0
function example(arg) {
  var argumentID = '0'; //1,2,3,4...whatever
  if (argumentID in arguments === false) {
    console.log(`the argument with id ${argumentID} was not passed to the function`);
  }
}

Ponieważ tablice dziedziczą Object.prototype. Zastanów się ⇑, aby uczynić świat lepszym.

Podniesiony
źródło
-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

Jeśli powyższa funkcja jest wywoływana z wielu miejsc i masz pewność, że pierwsze 2 parametry są przekazywane z każdego miejsca, ale nie jesteś pewien co do trzeciego parametru, możesz użyć okna

Window.param3 obsłuży, jeśli nie jest zdefiniowane w metodzie wywołującej.

Goutam Panda
źródło