Il modo migliore per determinare se un argomento non viene inviato alla funzione JavaScript


236

Ora ho visto 2 metodi per determinare se un argomento è stato passato a una funzione JavaScript. Mi chiedo se un metodo sia migliore dell'altro o se uno sia solo male da usare?

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

      alert(argument2);
 }

 Test('test');

O

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

      alert(argument2);
 }

 Test('test');

Per quanto ne so, entrambi risultano nella stessa cosa, ma ho usato solo il primo prima in produzione.

Un'altra opzione, come menzionato da Tom :

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

    alert(argument2);
}

Secondo il commento di Juan, sarebbe meglio cambiare il suggerimento di Tom in:

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

    alert(argument2);
}

È davvero lo stesso. Se hai sempre un numero statico di argomenti, procedi con il secondo metodo, altrimenti è probabilmente più facile iterare usando l'array degli argomenti.
Luca Matteis,

7
Un argomento che non è stato approvato viene definito indefinito. Il test con l'uguaglianza rigorosa contro null avrà esito negativo. Dovresti usare l'uguaglianza rigorosa con undefined.
Juan Mendes,

19
Ricorda: argument2 || 'blah';risulterà in "blah" se argument2è false(!), Non semplicemente se non è definito. Se argument2è un valore booleano e la funzione viene passata falseper essa, quella riga restituirà "blah" nonostante argument2sia stata definita correttamente .
Sandy Gifford,

9
@SandyGifford: Stesso problema se argument2è 0, ''o null.
rvighne,

@rvighne Molto vero. L'interpretazione unica di Javascript degli oggetti e del casting è tutto insieme le parti migliori e peggiori.
Sandy Gifford,

Risposte:


274

Esistono diversi modi per verificare se un argomento è stato passato a una funzione. Oltre ai due che hai menzionato nella tua (originale) domanda - controllo arguments.lengtho utilizzo ||dell'operatore per fornire valori predefiniti - si possono anche verificare esplicitamente gli argomenti per undefinedvia argument2 === undefinedo typeof argument2 === 'undefined'se si è paranoici (vedere commenti).

Utilizzando l' ||operatore è diventata una pratica standard - tutti i bambini freschi farlo - ma attenzione: il valore predefinito verrà attivato se i Esamina argomento false, il che significa che potrebbe in realtà essere undefined, null, false, 0, ''(o qualsiasi altra cosa per la quale Boolean(...)i rendimenti false).

Quindi la domanda è quando usare quale controllo, dato che tutti producono risultati leggermente diversi.

Il controllo arguments.lengthmostra il comportamento "più corretto", ma potrebbe non essere fattibile se esiste più di un argomento facoltativo.

Il test per il undefinedprossimo è il "migliore" - "fallisce" solo se la funzione viene esplicitamente chiamata con un undefinedvalore, che con ogni probabilità dovrebbe essere trattato allo stesso modo dell'omissione dell'argomento.

L'uso ||dell'operatore potrebbe innescare l'utilizzo del valore predefinito anche se viene fornito un argomento valido. D'altra parte, il suo comportamento potrebbe effettivamente essere desiderato.

Riassumendo: usalo solo se sai cosa stai facendo!

A mio avviso, usare ||è anche la strada da percorrere se c'è più di un argomento facoltativo e non si vuole passare un oggetto letteralmente come soluzione alternativa per i parametri con nome.

Un altro bel modo per fornire valori predefiniti usando arguments.lengthè possibile scorrendo le etichette di un'istruzione 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
}

Questo ha il rovescio della medaglia che l'intenzione del programmatore non è (visivamente) ovvia e usa "numeri magici"; è quindi probabilmente soggetto a errori.


42
Dovresti effettivamente controllare typeof argomento2 === "undefined", nel caso in cui qualcuno definisca "undefined".
JW.

133
Aggiungerò un avviso, ma quali bastardi malati fanno cose del genere?
Christoph,

12
undefined è una variabile nello spazio globale. È più lento cercare quella variabile nell'ambito globale rispetto a una variabile nell'ambito locale. Ma il più veloce è usare typeof x === "non definito"
circa il

5
Interessante. D'altra parte, il confronto === indefinito potrebbe essere più veloce del confronto di stringhe. I miei test sembrano indicare che hai ragione, però: x === non definito necessario ~ 1,5 volte il tempo di tipo di x === 'non definito'
Christoph

4
@Cristoph: dopo aver letto il tuo commento, ho chiesto in giro. Sono stato in grado di dimostrare che il confronto tra stringhe non è sicuramente (solo) un confronto puntatore poiché il confronto di una stringa gigantesca richiede più tempo di una stringa piccola. Tuttavia, il confronto tra due stringhe gigantesche è molto lento solo se sono state costruite con la concatenazione, ma molto velocemente se la seconda è stata creata come assegnazione dalla prima. Quindi probabilmente funziona con un test puntatore seguito da un test lettera per lettera Quindi, sì, ero pieno di merda :) grazie per avermi illuminato ...
Juan Mendes,

17

Se stai usando jQuery, un'opzione che è piacevole (specialmente per situazioni complicate) è usare il metodo di estensione di jQuery .

function foo(options) {

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

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

Se si chiama la funzione, quindi in questo modo:

foo({timeout : 500});

La variabile opzioni sarebbe quindi:

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

15

Questo è uno dei pochi casi in cui trovo il test:

if(! argument2) {  

}

funziona abbastanza bene e porta sintatticamente le implicazioni corrette.

(Con la restrizione simultanea che non permetterei un valore nullo legittimo per il argument2quale ha qualche altro significato; ma sarebbe davvero confuso.)

MODIFICARE:

Questo è davvero un buon esempio di una differenza stilistica tra linguaggi tipicamente vaghi e fortemente tipizzati; e un'opzione stilistica che javascript offre a picche.

La mia preferenza personale (senza critiche per altre preferenze) è il minimalismo. Meno il codice ha da dire, fintanto che sono coerente e conciso, meno qualcun altro deve comprendere per dedurre correttamente il mio significato.

Un'implicazione di quella preferenza è che non voglio - non trovo utile - accumulare un mucchio di test di dipendenza dal tipo. Invece, provo a fare in modo che il codice significhi ciò che sembra significhi; e testare solo ciò per cui avrò davvero bisogno.

Una delle esasperazioni che trovo nel codice di alcune altre persone è la necessità di capire se, in un contesto più ampio, si aspettano effettivamente di imbattersi nei casi per cui stanno testando. O se stanno provando a testare tutto il possibile, sulla possibilità che non anticipino completamente il contesto. Ciò significa che alla fine ho bisogno di rintracciarli esaurientemente in entrambe le direzioni prima di poter refactificare o modificare con sicurezza qualsiasi cosa. Immagino che ci siano buone probabilità che abbiano messo in atto quei vari test perché prevedevano circostanze in cui sarebbero stati necessari (e che di solito non sono evidenti per me).

(Ritengo che un grave aspetto negativo nel modo in cui queste persone usano i linguaggi dinamici. Troppo spesso le persone non vogliono rinunciare a tutti i test statici e finiscono per fingere.)

L'ho visto in modo molto evidente nel confrontare il codice completo di ActionScript 3 con l'elegante codice javascript. L'AS3 può essere 3 o 4 volte la maggior parte dei js e l'affidabilità che sospetto non è almeno migliore, solo a causa del numero (3-4X) delle decisioni di codifica che sono state prese.

Come dici tu, Shog9, YMMV. : D


1
if (! argomento2) argomento2 = 'default' è equivalente a argomento2 = argomento2 || 'default' - Trovo la seconda versione visivamente più piacevole ...
Christoph

5
E lo trovo più prolisso e distraente; ma è una preferenza personale, ne sono sicuro.
dkretz,

4
@le dorfier: impedisce anche l'uso di stringhe vuote, 0 e false booleane.
Shog9

@le dorfier: oltre l'estetica, c'è una differenza chiave: quest'ultima crea effettivamente un secondo percorso di esecuzione, che potrebbe tentare i manutentori incuranti di aggiungere un comportamento oltre la semplice assegnazione di un valore predefinito. YMMV, ovviamente.
Shog9

3
cosa succede se parametro2 è un valore booleano === falso; o una funzione {return false;}?
Fresko,

7

Ci sono differenze significative. Impostiamo alcuni casi di test:

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

Con il primo metodo che descrivi, solo il secondo test utilizzerà il valore predefinito. Il secondo metodo non adempia tutti tranne il primo (come convertirà JS undefined, null, 0, e ""nella booleano false. E se si sceglie di usare il metodo di Tom, solo la quarta prova userà il default!

Quale metodo scegli dipende davvero dal tuo comportamento previsto. Se undefinedsono consentiti valori diversi da argument2, allora probabilmente vorrai qualche variazione sul primo; se si desidera un valore diverso da zero, non nullo, non vuoto, il secondo metodo è l'ideale, anzi, viene spesso utilizzato per eliminare rapidamente un intervallo così ampio di valori.


7
url = url === undefined ? location.href : url;

Risposta di ossa nude. Alcune spiegazioni non farebbero male.
Paul Rooney,

È un operatore ternario che dice in modo conciso: se l'URL non è definito (mancante), imposta la variabile url su location.href (la pagina Web corrente), altrimenti imposta la variabile url come url definito.
rmooney

5

In ES6 (ES2015) è possibile utilizzare i parametri predefiniti

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

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


Questa risposta è davvero interessante e potrebbe essere utile per op. Anche se in realtà non risponde alla domanda
Ulysse BN,

Come vedo - vuole definire arg se non è stato definito, quindi ho pubblicato alcune informazioni utili
Andriy2

Il punto è che non stai rispondendo al modo migliore per determinare se un argomento non viene inviato alla funzione JavaScript . Ma alla domanda si potrebbe rispondere usando argomenti predefiniti: ad esempio nominando gli argomenti con "default value"e verificando se il valore è effettivamente "default value".
Ulysse BN,

4

Mi dispiace, non posso ancora commentare, quindi per rispondere alla risposta di Tom ... In javascript (undefined! = Null) == false In effetti quella funzione non funzionerà con "null", dovresti usare "undefined"


1
E Tom ha ottenuto due voti positivi per una risposta sbagliata - è sempre bello sapere quanto funzionano bene questi sistemi di comunità;)
Christoph

3

Perché non usare l' !!operatore? Questo operatore, posto prima della variabile, la trasforma in un valore booleano (se ho capito bene), quindi !!undefinede !!null(e anche !!NaN, che può essere abbastanza interessante) tornerà false.

Ecco un esempio:

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

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

foo() //=> will log false

Non funziona con stringa booleana vera, zero e vuota. Ad esempio, foo (0); registrerà false, ma foo (1) registrerà true
rosell.dk il

2

Può essere conveniente avvicinarsi al rilevamento degli argomenti evocando la tua funzione con un oggetto con proprietà opzionali:

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]; }
            }
        }
    }
}

2

Esiste anche un modo complicato per scoprire se un parametro viene passato o meno a una funzione . Dai un'occhiata all'esempio seguente:

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

Ciò necessario significa che se il valore di valuenon è presente / passato, impostarlo su 0.

Abbastanza bello eh!


1
Questo in realtà significa "se valueè equivalente a false, impostalo su 0." Questa è una distinzione sottile ma molto importante.
Charles Wood,

@CharlesWood Il valore non è passato / il valore presente è solo falso
Mohammed Zameer

1
Certo, se questo soddisfa i requisiti per la tua funzione. Ma se, ad esempio, il tuo parametro è booleano, allora truee falsesono valori validi e potresti voler avere un terzo comportamento per quando il parametro non viene passato affatto (specialmente se la funzione ha più di un parametro).
Charles Wood,

1
E dovrei riconoscere che questa è una grande discussione nell'informatica e potrebbe finire per essere solo una questione di opinione: D
Charles Wood,

@CharlesWood mi dispiace per essere in ritardo alla festa. Ti suggerisco di aggiungerli nella risposta stessa con l'opzione di modifica
Mohammed Zameer,

1

Alcune volte potresti anche voler verificare il tipo, specialmente se stai usando la funzione come getter e setter. Il seguente codice è ES6 (non verrà eseguito in EcmaScript 5 o precedente):

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;
            }
        }
    }
}

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`);
  }
}

Perché le matrici ereditano da Object.prototype. Considera ⇑ di migliorare il mondo.


-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

Se la funzione di cui sopra viene chiamata da molti punti e sei sicuro che i primi 2 parametri vengano passati da ogni dove ma non sei sicuro del terzo parametro, puoi usare la finestra.

window.param3 gestirà se non è definito dal metodo del chiamante.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.