web-dev-qa-db-fra.com

Existe-t-il un moyen de fournir des paramètres nommés dans un appel de fonction en JavaScript?

Je trouve les paramètres nommés en C # très utiles dans certains cas.

calculateBMI(70, height: 175);

Que puis-je utiliser si je veux ceci en JavaScript?


Ce que je ne veux pas, c'est ceci:

myFunction({ param1: 70, param2: 175 });

function myFunction(params){
  // Check if params is an object
  // Check if the parameters I need are non-null
  // Blah blah
}

J'ai déjà utilisé cette approche. Y a-t-il un autre moyen?

Je peux utiliser n'importe quelle bibliothèque pour le faire.

152
Robin Maben

ES2015 et ultérieur

Dans ES2015, la déstructuration du paramètre peut être utilisé pour simuler des paramètres nommés. L'appelant doit transmettre un objet, mais vous pouvez éviter toutes les vérifications de la fonction si vous utilisez également les paramètres par défaut:

myFunction({ param1 : 70, param2 : 175});

function myFunction({param1, param2}={}){
  // ...function body...
}

// Or with defaults, 
function myFunc({
  name = 'Default user',
  age = 'N/A'
}={}) {
  // ...function body...
}

ES5

Il existe un moyen de se rapprocher de ce que vous voulez, mais cela dépend du résultat de Function.prototype.toString[ES5] , qui dépend de l'implémentation dans une certaine mesure, il est donc possible qu'il ne soit pas compatible avec plusieurs navigateurs.

L'idée est d'analyser les noms de paramètre à partir de la représentation sous forme de chaîne de la fonction afin d'associer les propriétés d'un objet au paramètre correspondant.

Un appel de fonction pourrait alors ressembler à

func(a, b, {someArg: ..., someOtherArg: ...});

a et b sont des arguments de position et le dernier argument est un objet avec des arguments nommés.

Par exemple:

var parameterfy = (function() {
    var pattern = /function[^(]*\(([^)]*)\)/;

    return function(func) {
        // fails horribly for parameterless functions ;)
        var args = func.toString().match(pattern)[1].split(/,\s*/);

        return function() {
            var named_params = arguments[arguments.length - 1];
            if (typeof named_params === 'object') {
                var params = [].slice.call(arguments, 0, -1);
                if (params.length < args.length) {
                    for (var i = params.length, l = args.length; i < l; i++) {
                        params.Push(named_params[args[i]]);
                    }
                    return func.apply(this, params);
                }
            }
            return func.apply(null, arguments);
        };
    };
}());

Ce que vous utiliseriez comme:

var foo = parameterfy(function(a, b, c) {
    console.log('a is ' + a, ' | b is ' + b, ' | c is ' + c);     
});

foo(1, 2, 3); // a is 1  | b is 2  | c is 3
foo(1, {b:2, c:3}); // a is 1  | b is 2  | c is 3
foo(1, {c:3}); // a is 1  | b is undefined  | c is 3
foo({a: 1, c:3}); // a is 1  | b is undefined  | c is 3 

DÉMO

Il y a quelques inconvénients à cette approche (vous avez été prévenus!):

  • Si le dernier argument est un objet, il est traité comme un "objet à argument nommé"
  • Vous obtiendrez toujours autant d'arguments que ceux que vous avez définis dans la fonction, mais certains d'entre eux pourraient avoir la valeur undefined (c'est différent de ne pas avoir de valeur du tout). Cela signifie que vous ne pouvez pas utiliser arguments.length pour tester le nombre d'arguments passés.

Au lieu d’avoir une fonction créant le wrapper, vous pouvez également avoir une fonction qui accepte une fonction et diverses valeurs en tant qu’arguments, tels que

call(func, a, b, {posArg: ... });

ou même étendre Function.prototype afin que vous puissiez faire:

foo.execute(a, b, {posArg: ...});
149
Felix Kling

Non - l'approche objet est la réponse de JavaScript à cela. Cela ne pose pas de problème, à condition que votre fonction attende un objet plutôt que des paramètres séparés.

58
Utkanos

Ce numéro est une bête noire pour moi depuis quelque temps. Je suis un programmeur expérimenté avec de nombreuses langues à mon actif. L’un de mes langages préférés que j’ai eu le plaisir d’utiliser est le python. Python supporte les paramètres nommés sans aucune astuce ... Depuis que j'ai commencé à utiliser Python (il y a quelque temps), tout est devenu plus facile. Je crois que chaque langue devrait supporter les paramètres nommés, mais ce n'est tout simplement pas le cas.

Beaucoup de gens disent d'utiliser simplement l'astuce "Passer un objet" pour que vous ayez nommé des paramètres.

/**
 * My Function
 *
 * @param {Object} arg1 Named arguments
 */
function myFunc(arg1) { }

myFunc({ param1 : 70, param2 : 175});

Et cela fonctionne très bien, sauf que ... pour la plupart des IDE, beaucoup de développeurs se fient aux indications de type/argument de notre IDE. Personnellement, j'utilise PHP Storm (avec d'autres IDE JetBrains comme PyCharm pour python et AppCode pour Objective C)

Et le plus gros problème avec l'utilisation de l'astuce "Passer un objet" est que lorsque vous appelez la fonction, le IDE vous donne un indice de type unique et c'est tout ... Comment pouvons-nous savoir quels paramètres et les types devraient aller dans l'objet arg1?

I have no idea what parameters should go in arg1

Donc ... le truc "Passer un objet" ne marche pas pour moi ... Cela cause en fait plus de maux de tête lorsque je dois regarder le docblock de chaque fonction avant que je sache quels paramètres la fonction attend ... Bien sûr, c'est génial pour lorsque vous maintenez le code existant, mais que c'est horrible d'écrire du nouveau code.

Eh bien, c’est la technique que j’utilise ... Maintenant, il peut y avoir des problèmes, et certains développeurs peuvent me dire que je ne le fais pas bien, et que je suis ouvert d’esprit quand il s’agit de ces choses ... Je suis toujours disposé à rechercher de meilleurs moyens d'accomplir une tâche ... Donc, s'il y a un problème avec cette technique, les commentaires sont les bienvenus.

/**
 * My Function
 *
 * @param {string} arg1 Argument 1
 * @param {string} arg2 Argument 2
 */
function myFunc(arg1, arg2) { }

var arg1, arg2;
myFunc(arg1='Param1', arg2='Param2');

De cette façon, j’ai le meilleur des deux mondes ... le nouveau code est facile à écrire car mon IDE me donne tous les conseils sur les arguments appropriés ... Et, tout en conservant le code plus tard, je peux voir à un coup d’œil, non seulement la valeur transmise à la fonction, mais également le nom de l’argument. La seule surcharge que je vois est de déclarer vos noms d'arguments en tant que variables locales afin d'éviter de polluer l'espace de noms global. Bien sûr, il s'agit d'un peu de frappe supplémentaire, mais trivial par rapport au temps nécessaire pour rechercher des docblocks lors de l'écriture de nouveau code ou de la maintenance du code existant.

Now, I have all the parameters and types when creating new code

25
Ray Perea

Si vous voulez préciser chacun des paramètres plutôt que d'appeler

someFunction(70, 115);

pourquoi ne pas faire ce qui suit

var width = 70, height = 115;
someFunction(width, height);

bien sûr, c'est une ligne de code supplémentaire, mais elle gagne en lisibilité.

18
dav_i

Une autre méthode consisterait à utiliser les attributs d’un objet approprié, par exemple. ainsi:

function plus(a,b) { return a+b; };

Plus = { a: function(x) { return { b: function(y) { return plus(x,y) }}}, 
         b: function(y) { return { a: function(x) { return plus(x,y) }}}};

sum = Plus.a(3).b(5);

Bien sûr, pour cet exemple inventé, cela n'a pas beaucoup de sens. Mais dans les cas où la fonction ressemble à

do_something(some_connection_handle, some_context_parameter, some_value)

cela pourrait être plus utile. Il pourrait également être associé à l’idée de "paramétrage" pour créer un tel objet à partir d’une fonction existante de manière générique. C’est-à-dire que pour chaque paramètre, il créerait un membre capable d’évaluer une version partiellement évaluée de la fonction.

Cette idée est bien sûr liée à Schönfinkeling aka Currying.

6
Udo Klein

Il y a un autre moyen. Si vous transmettez un objet par référence, les propriétés de cet objet apparaîtront dans la portée locale de la fonction. Je sais que cela fonctionne pour Safari (n’a pas vérifié les autres navigateurs) et je ne sais pas si cette fonctionnalité a un nom, mais l’exemple ci-dessous illustre son utilisation.

Bien qu'en pratique, je ne pense pas que cela offre une valeur fonctionnelle au-delà de la technique que vous utilisez déjà, c'est un peu plus propre sur le plan sémantique. Et il faut toujours passer une référence d'objet ou un littéral d'objet.

function sum({ a:a, b:b}) {
    console.log(a+'+'+b);
    if(a==undefined) a=0;
    if(b==undefined) b=0;
    return (a+b);
}

// will work (returns 9 and 3 respectively)
console.log(sum({a:4,b:5}));
console.log(sum({a:3}));

// will not work (returns 0)
console.log(sum(4,5));
console.log(sum(4));
2
Allen Ellison

Essayer Node-6.4.0 (process.versions.v8 = '5.0.71.60') et Node Chakracore-v7.0.0-pre8 puis Chrome-52 (V8 = 5.2.361.49), j'ai remarqué que les paramètres nommés sont presque implémentés, mais cet ordre a toujours la priorité. Je ne trouve pas ce que dit la norme ECMA.

>function f(a=1, b=2){ console.log(`a=${a} + b=${b} = ${a+b}`) }

> f()
a=1 + b=2 = 3
> f(a=5)
a=5 + b=2 = 7
> f(a=7, b=10)
a=7 + b=10 = 17

Mais l'ordre est nécessaire !! Est-ce le comportement standard?

> f(b=10)
a=10 + b=2 = 12
1
jgran

Venant de Python, cela m’a perturbé. J'ai écrit un wrapper/proxy simple pour le noeud qui exclura les objets positionnels et mots-clés.

https://github.com/vinces1979/node-def/blob/master/README.md

0
Vince Spicer

Appel de la fonction f avec les paramètres nommés passés en tant qu'objet

o = {height: 1, width: 5, ...}

appelle fondamentalement sa composition f(...g(o)) où j'utilise la syntaxe étendue et g est une carte "obligatoire" reliant les valeurs d'objet avec leurs positions de paramètre.

La carte de liaison est précisément l'ingrédient manquant, qui peut être représenté par le tableau de ses clés:

// map 'height' to the first and 'width' to the second param
binding = ['height', 'width']

// take binding and arg object and return aray of args
withNamed = (bnd, o) => bnd.map(param => o[param])

// call f with named args via binding
f(...withNamed(binding, {hight: 1, width: 5}))

Notez le trois ingrédients découplés: la fonction, l'objet avec les arguments nommés et la liaison. Ce découplage offre beaucoup de flexibilité pour utiliser cette construction, où la liaison peut être arbitrairement personnalisée dans la définition de la fonction et étendue de manière arbitraire au moment de l'appel de la fonction.

Par exemple, vous voudrez peut-être abréger height et width comme h et w dans la définition de votre fonction, pour la raccourcir et la nettoyer, tout en l'appelant. avec des noms complets pour plus de clarté:

// use short params
f = (h, w) => ...

// modify f to be called with named args
ff = o => f(...withNamed(['height', 'width'], o))

// now call with real more descriptive names
ff({height: 1, width: 5})

Cette flexibilité est également plus utile pour la programmation fonctionnelle, où les fonctions peuvent être transformées de manière arbitraire en perdant leurs noms de paramètres d'origine.

0
Dmitri Zaitsev