Comment déterminer au mieux si un argument n'est pas envoyé à la fonction JavaScript

236

J'ai maintenant vu 2 méthodes pour déterminer si un argument a été passé à une fonction JavaScript. Je me demande si une méthode est meilleure que l'autre ou si l'une est tout simplement mauvaise à utiliser?

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

      alert(argument2);
 }

 Test('test');

Ou

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

      alert(argument2);
 }

 Test('test');

Pour autant que je sache, ils aboutissent tous deux à la même chose, mais je n'ai utilisé que le premier avant en production.

Une autre option mentionnée par Tom :

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

    alert(argument2);
}

Selon le commentaire de Juan, il serait préférable de changer la suggestion de Tom pour:

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

    alert(argument2);
}
Darryl Hein
la source
C'est vraiment pareil. Si vous avez toujours un nombre statique d'arguments, optez pour la deuxième méthode, sinon il est probablement plus facile d'itérer en utilisant le tableau des arguments.
Luca Matteis
7
Un argument qui n'a pas été transmis apparaît comme non défini. Le test avec une égalité stricte contre null échouera. Vous devez utiliser une égalité stricte avec undefined.
Juan Mendes
19
Rappelez-vous: argument2 || 'blah';résultera en «bla» si argument2est false(!), Pas simplement s'il n'est pas défini. Si argument2est un booléen et que la fonction est passée falsepour cela, cette ligne retournera 'blah' bien qu'elle argument2soit correctement définie .
Sandy Gifford
9
@SandyGifford: Même problème si argument2est 0, ''ou null.
rvighne
@rvighne Très vrai. L'interprétation unique des objets et du casting par Javascript est à la fois la meilleure et la pire des parties.
Sandy Gifford

Réponses:

274

Il existe plusieurs façons de vérifier si un argument a été passé à une fonction. En plus des deux que vous avez mentionnés dans votre question (d'origine) - vérifier arguments.lengthou utiliser l' ||opérateur pour fournir des valeurs par défaut - on peut également vérifier explicitement les arguments undefinedvia argument2 === undefinedou typeof argument2 === 'undefined'si l'un est paranoïaque (voir commentaires).

Utilisation de l' ||opérateur est devenu pratique courante - tous les enfants de frais le faire - mais attention: La valeur par défaut est déclenchée si les evalue argument pour false, ce qui signifie qu'il pourrait en fait être undefined, null, false, 0, ''(ou toute autre chose dont le Boolean(...)rendement false).

La question est donc de savoir quand utiliser quelle vérification, car ils donnent tous des résultats légèrement différents.

Vérification arguments.length présente le comportement «le plus correct», mais il pourrait ne pas être possible s'il y a plus d'un argument facultatif.

Le test pour undefinedest ensuite «meilleur» - il «échoue» uniquement si la fonction est explicitement appelée avec unundefined valeur, qui, selon toute vraisemblance, doit être traitée de la même manière que l'omission de l'argument.

L'utilisation du || opérateur peut déclencher l'utilisation de la valeur par défaut même si un argument valide est fourni. D'un autre côté, son comportement pourrait en fait être souhaité.

Pour résumer: utilisez-le uniquement si vous savez ce que vous faites!

À mon avis, l'utilisation ||est également la voie à suivre s'il y a plus d'un argument facultatif et que l'on ne veut pas passer un littéral d'objet comme solution de contournement pour les paramètres nommés.

Une autre façon intéressante de fournir des valeurs par défaut en utilisant arguments.lengthest possible en passant par les étiquettes d'une instruction 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
}

Cela a l'inconvénient que l'intention du programmeur n'est pas (visuellement) évidente et utilise des «nombres magiques»; il est donc probablement sujet aux erreurs.

Christoph
la source
42
Vous devriez en fait vérifier typeof argument2 === "undefined", au cas où quelqu'un définirait "undefined".
JW.
133
Je vais ajouter un avis - mais quels salauds malades font des choses comme ça?
Christoph
12
undefined est une variable dans l'espace global. Il est plus lent de rechercher cette variable dans la portée globale qu'une variable dans une portée locale. Mais le plus rapide est d'utiliser typeof x === "undefined"
certains
5
Intéressant. D'un autre côté, la comparaison === non définie peut être plus rapide que la comparaison de chaînes. Mes tests semblent indiquer que vous avez raison, cependant: x === indéfini nécessaire ~ 1,5 fois le temps de typeof x === 'indéfini'
Christoph
4
@Cristoph: après avoir lu votre commentaire, j'ai demandé autour. J'ai pu prouver que la comparaison de chaînes n'est sûrement pas (seulement) par comparaison de pointeurs car comparer une chaîne gigantesque prend plus de temps qu'une petite chaîne. Cependant, la comparaison de deux chaînes gigantesques n'est vraiment lente que si elles ont été créées avec concaténation, mais très rapide si la seconde a été créée comme une affectation à partir de la première. Donc ça fonctionne probablement par un test de pointeur suivi d'un test lettre par lettre Alors, oui, j'étais plein de merde :) merci de m'éclairer ...
Juan Mendes
17

Si vous utilisez jQuery, une option intéressante (en particulier pour les situations compliquées) consiste à utiliser la méthode extend de jQuery .

function foo(options) {

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

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

Si vous appelez la fonction alors comme ceci:

foo({timeout : 500});

La variable d'options serait alors:

{
    timeout : 500,
    callback : function(){},
    some_number : 50,
    some_text : "hello world"
};
Greg Tatum
la source
15

C'est l'un des rares cas où je trouve le test:

if(! argument2) {  

}

fonctionne très bien et porte la bonne implication syntaxiquement.

(Avec la restriction simultanée que je n'autoriserais pas une valeur nulle légitime pour argument2laquelle a une autre signification; mais ce serait vraiment déroutant.)

ÉDITER:

Ceci est un très bon exemple de différence stylistique entre les langages peu typés et les types fortement typés; et une option stylistique que javascript offre à la pelle.

Ma préférence personnelle (sans critique pour les autres préférences) est le minimalisme. Moins le code a à dire, tant que je suis cohérent et concis, moins quelqu'un d'autre doit comprendre pour inférer correctement ma signification.

Une implication de cette préférence est que je ne veux pas - ne trouve pas utile de - empiler un tas de tests de dépendance de type. Au lieu de cela, j'essaie de faire en sorte que le code signifie à quoi il ressemble; et tester uniquement pour ce que je devrai vraiment tester.

L'une des aggravations que je trouve dans le code de certains autres peuples est de savoir s'ils s'attendent ou non, dans un contexte plus large, à se heurter réellement aux cas pour lesquels ils testent. Ou s'ils essaient de tester tout ce qui est possible, au cas où ils n'anticipent pas suffisamment le contexte. Ce qui signifie que je dois finir par les retrouver de manière exhaustive dans les deux sens avant de pouvoir refactoriser ou modifier quoi que ce soit en toute confiance. Je pense qu'il y a de fortes chances qu'ils aient mis ces différents tests en place car ils prévoyaient des circonstances où ils seraient nécessaires (et qui ne me sont généralement pas apparents).

(Je considère que c'est un sérieux inconvénient dans la façon dont ces gens utilisent les langages dynamiques. Trop souvent, les gens ne veulent pas abandonner tous les tests statiques et finissent par les simuler.)

J'ai vu cela de façon éclatante en comparant un code ActionScript 3 complet avec un code javascript élégant. L'AS3 peut être 3 ou 4 fois plus gros que les js, et la fiabilité que je soupçonne n'est au moins pas meilleure, simplement en raison du nombre (3-4X) de décisions de codage qui ont été prises.

Comme vous le dites, Shog9, YMMV. :RÉ

dkretz
la source
1
si (! argument2) argument2 = 'default' est équivalent à argument2 = argument2 || 'default' - Je trouve la deuxième version visuellement plus agréable ...
Christoph
5
Et je le trouve plus verbeux et distrayant; mais c'est une préférence personnelle, j'en suis sûr.
dkretz
4
@le dorfier: il interdit également l'utilisation de chaînes vides, 0 et booléen false.
Shog9
@le dorfier: au-delà de l'esthétique, il y a une différence clé: cette dernière crée effectivement un deuxième chemin d'exécution, ce qui pourrait inciter les responsables imprudents à ajouter un comportement au-delà de la simple affectation d'une valeur par défaut. YMMV, bien sûr.
Shog9
3
que faire si le paramètre2 est un booléen === false; ou une fonction {return false;}?
fresko
7

Il existe des différences importantes. Configurons quelques cas de test:

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

Avec la première méthode que vous décrivez, seul le deuxième test utilisera la valeur par défaut. La deuxième méthode par défaut , mais tout le premier (comme JS convertira undefined, null, 0et ""dans le booléen false. Et si vous deviez utiliser la méthode de Tom, seul le quatrième test utilisera la valeur par défaut!

La méthode que vous choisissez dépend vraiment de votre comportement prévu. Si des valeurs autres que celles undefinedautorisées sont acceptables argument2, alors vous voudrez probablement des variations sur la première; si une valeur non nulle, non nulle et non vide est souhaitée, alors la deuxième méthode est idéale - en effet, elle est souvent utilisée pour éliminer rapidement une si large gamme de valeurs.

Shog9
la source
7
url = url === undefined ? location.href : url;
Lamy
la source
Les os nus répondent. Une explication ne ferait pas de mal.
Paul Rooney
C'est un opérateur ternaire qui dit de manière concise: Si l'url n'est pas définie (manquante), définissez la variable url sur location.href (la page Web actuelle), sinon définissez la variable url sur l'url définie.
rmooney
5

Dans ES6 (ES2015), vous pouvez utiliser les paramètres par défaut

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

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

Andriy2
la source
Cette réponse est vraiment intéressante et pourrait être utile à op. Même si cela ne répond pas vraiment à la question
Ulysse BN
Comme je le vois - il veut définir l'argument s'il n'a pas été défini, j'ai donc posté des informations utiles
Andriy2
Mon point est que vous ne répondez pas à la meilleure façon de déterminer si un argument n'est pas envoyé à la fonction JavaScript . Mais la question pourrait être résolue en utilisant des arguments par défaut: par exemple, vous nommer des arguments avec "default value"et vérifier si la valeur est bien "default value".
Ulysse BN
4

Je suis désolé, je n'ai pas encore de commentaire, donc pour répondre à la réponse de Tom ... En javascript (undefined! = Null) == false En fait, cette fonction ne fonctionnera pas avec "null", vous devez utiliser "undefined"

Luca Matteis
la source
1
Et Tom a obtenu deux votes positifs pour une mauvaise réponse - c'est toujours agréable de savoir à quel point ces systèmes communautaires fonctionnent;)
Christoph
3

Pourquoi ne pas utiliser l' !!opérateur? Cet opérateur, placé avant la variable, la transforme en booléen (si j'ai bien compris), donc !!undefinedet !!null(et même !!NaN, ce qui peut être assez intéressant) reviendra false.

Voici un exemple:

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

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

foo() //=> will log false
RallionRl
la source
Ne fonctionne pas avec les chaînes booléennes true, zero et empty. Par exemple, foo (0); enregistrera faux, mais foo (1) enregistrera vrai
rosell.dk
2

Il peut être pratique d'aborder la détection d'arguments en évoquant votre fonction avec un objet de propriétés facultatives:

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
la source
2

Il existe également un moyen délicat de trouver, si un paramètre est passé à une fonction ou non . Jetez un œil à l'exemple ci-dessous:

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

Cela signifie que si la valeur de value n'est pas présente / transmise - définissez-la sur 0.

Assez cool hein!

Mohammed Zameer
la source
1
Cela signifie en fait "si valueest équivalent à false, mettez-le à 0". Il s'agit d'une distinction subtile mais très importante.
Charles Wood
@CharlesWood La valeur n'est pas transmise / le moyen actuel est faux uniquement
Mohammed Zameer
1
Bien sûr, si cela correspond aux exigences de votre fonction. Mais si, par exemple, votre paramètre est booléen, alors trueet falsesont des valeurs valides, et vous souhaiterez peut-être avoir un troisième comportement lorsque le paramètre n'est pas du tout passé (surtout si la fonction a plus d'un paramètre).
Charles Wood
1
Et je dois reconnaître que c'est un énorme argument en informatique et peut finir par être une question d'opinion: D
Charles Wood
@CharlesWood désolé d'être en retard à la fête. Je vous suggère d'ajouter ceux-ci dans la réponse elle-même avec l'option d'édition
Mohammed Zameer
1

Parfois, vous pouvez également vérifier le type, surtout si vous utilisez la fonction comme getter et setter. Le code suivant est ES6 (ne fonctionnera pas dans EcmaScript 5 ou plus ancien):

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

Parce que les tableaux héritent de Object.prototype. Considérez ⇑ pour rendre le monde meilleur.

Élevé
la source
-1

fnCalledFunction (Param1, Param2, window.YourOptionalParameter)

Si la fonction ci-dessus est appelée depuis de nombreux endroits et que vous êtes sûr que les 2 premiers paramètres sont passés de partout, mais que vous n'êtes pas sûr du 3e paramètre, vous pouvez utiliser la fenêtre.

window.param3 gérera s'il n'est pas défini à partir de la méthode de l'appelant.

Goutam Panda
la source