Lors de la création d'une fonction JavaScript avec plusieurs arguments, je suis toujours confronté à ce choix: transmettre une liste d'arguments ou transmettre un objet options.
Par exemple, j'écris une fonction pour mapper une liste de noeuds sur un tableau:
function map(nodeList, callback, thisObject, fromIndex, toIndex){
...
}
Je pourrais plutôt utiliser ceci:
function map(options){
...
}
où options est un objet:
options={
nodeList:...,
callback:...,
thisObject:...,
fromIndex:...,
toIndex:...
}
Lequel est le moyen recommandé? Existe-t-il des directives pour savoir quand utiliser l'un par rapport à l'autre?
[Mise à jour] Il semble y avoir un consensus en faveur de l'objet options, aussi j'aimerais ajouter un commentaire: l'une des raisons pour lesquelles j'ai été tenté d'utiliser la liste des arguments dans mon cas était d'avoir un comportement conforme à JavaScript construit dans la méthode array.map.
Comme beaucoup d’autres, je préfère souvent passer un options object
à une fonction au lieu de passer une longue liste de paramètres, mais cela dépend vraiment du contexte exact.
J'utilise la lisibilité du code comme test décisif.
Par exemple, si j'ai cette fonction appeler:
checkStringLength(inputStr, 10);
Je pense que le code est assez lisible tel quel et que passer des paramètres individuels convient parfaitement.
D'autre part, il existe des fonctions avec des appels comme celui-ci:
initiateTransferProtocol("http", false, 150, 90, null, true, 18);
Complètement illisible à moins de faire des recherches. Par contre, ce code se lit bien:
initiateTransferProtocol({
"protocol": "http",
"sync": false,
"delayBetweenRetries": 150,
"randomVarianceBetweenRetries": 90,
"retryCallback": null,
"log": true,
"maxRetries": 18
});
C'est plus un art qu'une science, mais si je devais nommer des règles empiriques:
Utilisez un paramètre d'options si:
Les arguments multiples sont principalement des paramètres obligatoires. Il n'y a rien de mal avec eux.
Si vous avez des paramètres optionnels, cela devient compliqué. Si l'un d'entre eux s'appuie sur les autres pour qu'ils aient un certain ordre (par exemple, le quatrième a besoin du troisième), vous devez toujours utiliser plusieurs arguments. Presque toutes les méthodes natives EcmaScript et DOM fonctionnent comme ceci. Un bon exemple est la méthode open
de XMLHTTPrequests , où les 3 derniers arguments sont facultatifs. La règle est la suivante: "pas de mot de passe sans utilisateur" (voir aussi docs MDN ).
Les objets option sont utiles dans deux cas:
undefined
s).Dans votre cas, je recommanderais map(nodeList, callback, options)
. nodelist
et callback
sont obligatoires, les trois autres arguments n'interviennent qu'occasionnellement et ont des valeurs par défaut raisonnables.
Un autre exemple est JSON.stringify
. Vous voudrez peut-être utiliser le paramètre space
sans transmettre de fonction replacer
; vous devrez alors appeler …, null, 4)
. Un objet arguments aurait peut-être été meilleur, bien que ce ne soit pas vraiment raisonnable pour seulement 2 paramètres.
Utiliser l'approche 'options en tant qu'objet' sera préférable. Vous n'avez pas à vous soucier de l'ordre des propriétés et il y a plus de flexibilité dans les données transmises (paramètres facultatifs par exemple)
La création d'un objet signifie également que les options pourraient être facilement utilisées sur plusieurs fonctions:
options={
nodeList:...,
callback:...,
thisObject:...,
fromIndex:...,
toIndex:...
}
function1(options){
alert(options.nodeList);
}
function2(options){
alert(options.fromIndex);
}
Je pense que si vous instanciez quelque chose ou appelez une méthode d'objet, vous voulez utiliser un objet d'options. S'il s'agit d'une fonction qui opère sur un ou deux paramètres et renvoie une valeur, une liste d'arguments est préférable.
Dans certains cas, il est bon d'utiliser les deux. Si votre fonction a un ou deux paramètres obligatoires et une série d'options facultatives, définissez les deux premiers paramètres comme obligatoires et le troisième un hachage d'options facultatif.
Dans votre exemple, je ferais map(nodeList, callback, options)
. Nodelist et callback sont nécessaires, il est assez facile de savoir ce qui se passe simplement en lisant un appel, et cela ressemble aux fonctions de carte existantes. Toute autre option peut être passée en tant que troisième paramètre facultatif.
Votre commentaire sur la question:
dans mon exemple, les trois derniers sont optionnels.
Alors pourquoi ne pas faire ça? (Note: Ceci est un Javascript assez brut. Normalement, j'utiliserais un hash default
et le mettrais à jour avec les options passées en utilisant Object.extend = ou JQuery.extend ou similaire ..)
function map(nodeList, callback, options) {
options = options || {};
var thisObject = options.thisObject || {};
var fromIndex = options.fromIndex || 0;
var toIndex = options.toIndex || 0;
}
Donc, maintenant qu'il est beaucoup plus évident de choisir entre ce qui est optionnel et ce qui ne l'est pas, toutes ces utilisations sont valables:
map(nodeList, callback);
map(nodeList, callback, {});
map(nodeList, callback, null);
map(nodeList, callback, {
thisObject: {some: 'object'},
});
map(nodeList, callback, {
toIndex: 100,
});
map(nodeList, callback, {
thisObject: {some: 'object'},
fromIndex: 0,
toIndex: 100,
});
Ça dépend.
D'après mes observations sur la conception de ces bibliothèques populaires, voici les scénarios que nous devrions utiliser avec l'option option:
Et des scénarios à utiliser la liste de paramètres:
Je suis peut-être un peu en retard à la fête avec cette réponse, mais je recherchais l'opinion d'autres développeurs sur ce sujet même et suis tombé sur ce fil.
Je suis très en désaccord avec la plupart des intervenants et je souscris à l'approche des "arguments multiples". Mon argument principal est que cela décourage les autres anti-patterns tels que "la mutation et le retour de l'objet param", ou "la transmission du même objet param à d'autres fonctions". J'ai travaillé dans des bases de code qui ont largement abusé de cet anti-motif, et le code de débogage qui le fait devient rapidement impossible. Je pense que c'est une règle empirique très spécifique à Javascript, car Javascript n'est pas fortement typé et permet de tels objets structurés de manière arbitraire.
Mon opinion personnelle est que les développeurs doivent être explicites lors de l'appel de fonctions, éviter de transmettre des données redondantes et d'éviter de les modifier par référence. Ce n’est pas que cela empêche la rédaction d’un code concis et correct. Je pense simplement que cela facilite beaucoup la tâche de votre projet de tomber dans de mauvaises pratiques de développement.
Considérez le code terrible suivant:
function main() {
const x = foo({
param1: "something",
param2: "something else",
param3: "more variables"
});
return x;
}
function foo(params) {
params.param1 = "Something new";
bar(params);
return params;
}
function bar(params) {
params.param2 = "Something else entirely";
const y = baz(params);
return params.param2;
}
function baz(params) {
params.params3 = "Changed my mind";
return params;
}
Non seulement cela nécessite une documentation plus explicite pour spécifier l'intention, mais cela laisse également de la place à de vagues erreurs. Que faire si un développeur modifie param1
Dans bar()
? Combien de temps pensez-vous qu'il faudrait chercher dans une base de code de taille suffisante pour comprendre cela? Certes, cet exemple est légèrement fallacieux car il suppose que les développeurs ont déjà commis plusieurs anti-patterns à ce stade. Mais il montre comment le fait de passer des objets contenant des paramètres laisse plus de place à l’erreur et à l’ambiguïté, exigeant un degré de conscience supérieur et le respect de la correction correcte.
Juste mes deux centimes sur la question!
L'objet est préférable, car si vous transmettez un objet, il est facile d'étendre le nombre de propriétés dans cet objet et vous n'avez pas à surveiller l'ordre dans lequel vos arguments ont été passés.
Pour une fonction qui utilise généralement des arguments prédéfinis, vous feriez mieux d'utiliser un objet option. L'exemple opposé sera quelque chose comme une fonction qui obtient un nombre infini d'arguments comme: setCSS ({height: 100}, {width: 200}, {background: "# 000"}).
Je regarderais de gros projets javascript.
Des choses comme Google Map, vous verrez fréquemment que les objets instanciés nécessitent un objet mais que les fonctions nécessitent des paramètres. Je pense que cela a à voir avec OPG argumemnts.
Si vous avez besoin d'arguments par défaut ou optionnels, un objet sera probablement préférable car il est plus flexible. Mais si vous n'êtes pas normal, les arguments fonctionnels sont plus explicites.
Javascript a aussi un objet arguments
. https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments