web-dev-qa-db-fra.com

Comment obtenir des noms/valeurs de paramètres de fonction de manière dynamique?

Existe-t-il un moyen d'obtenir les noms de paramètre de fonction d'une fonction de manière dynamique?

Disons que ma fonction ressemble à ceci:

function doSomething(param1, param2, .... paramN){
   // fill an array with the parameter name and value
   // some other code 
}

Maintenant, comment pourrais-je obtenir une liste des noms de paramètres et leurs valeurs dans un tableau à l'intérieur de la fonction?

270
vikasde

La fonction suivante renverra un tableau des noms de paramètres de toutes les fonctions transmises.

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
  var fnStr = func.toString().replace(STRIP_COMMENTS, '');
  var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
  if(result === null)
     result = [];
  return result;
}

Exemple d'utilisation:

getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []

Modifier:

Avec l’invention de l’ES6, cette fonction peut être déclenchée par des paramètres par défaut. Voici un petit hack qui devrait fonctionner dans la plupart des cas:

var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

Je dis la plupart des cas parce qu'il y a des choses qui vont trébucher

function (a=4*(5/3), b) {} // returns ['a']

Edit: Je note également que vikasde veut également les valeurs des paramètres dans un tableau. Ceci est déjà fourni dans une variable locale nommée arguments. 

extrait de https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments :

L'objet arguments n'est pas un tableau. Il est similaire à un tableau, mais ne possède aucune propriété de tableau à l'exception de la longueur. Par exemple, il n'a pas la méthode pop. Cependant, il peut être converti en un véritable tableau:

var args = Array.prototype.slice.call(arguments);

Si des génériques de tableau sont disponibles, vous pouvez utiliser les éléments suivants:

var args = Array.slice(arguments);
298
Jack Allan

Ci-dessous est le code extrait de AngularJS qui utilise cette technique pour son mécanisme d’injection de dépendance.

Et voici une explication de cela tirée de http://docs.angularjs.org/tutorial/step_05

L'injecteur de dépendance d'Angular fournit des services à votre contrôleur lors de la construction du contrôleur. L'injecteur de dépendance également se charge de créer toutes les dépendances transitives que le service peut générer ont (les services dépendent souvent d’autres services).

Notez que les noms des arguments sont significatifs, car l'injecteur les utilise pour rechercher les dépendances.

/**
 * @ngdoc overview
 * @name AUTO
 * @description
 *
 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
 */

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
        arg.replace(FN_ARG, function(all, underscore, name){
          $inject.Push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn')
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}
117
Lambder

Voici une solution mise à jour qui tente de traiter tous les cas Edge mentionnés ci-dessus de manière compacte:

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

Résultat de test abrégé (les scénarios de test complets sont joints ci-dessous):

'function (a,b,c)...' // returns ["a","b","c"]
'function ()...' // returns []
'function named(a, b, c) ...' // returns ["a","b","c"]
'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
'function( a, b = 1, c )...' // returns ["a","b","c"]
'function (a=4*(5/3), b) ...' // returns ["a","b"]
'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
'function (a /* fooled you...' // returns ["a","b"]
'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
'function (a,b)...' // returns ["a","b"]
'function $args(func) ...' // returns ["func"]
'null...' // returns ["null"]
'function Object() ...' // returns []

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

// test cases  
document.getElementById('console_info').innerHTML = (
[  
  // formatting -- typical  
  function(a,b,c){},  
  function(){},  
  function named(a, b,  c) {  
/* multiline body */  
  },  
    
  // default values -- conventional  
  function(a /* = 1 */, b /* = true */) { a = a||1; b=b||true; },  
  function fprintf(handle, fmt /*, ...*/) { },  
  
  // default values -- ES6  
  "function( a, b = 1, c ){}",  
  "function (a=4*(5/3), b) {}",  
  
  // embedded comments -- sardonic  
  function(a, // single-line comment xjunk) {}
    b //,c,d
  ) // single-line comment
  {},  
  function(a /* fooled you{*/,b){},  
  function /* are you kidding me? (){} */(a /* function() yes */,  
   /* no, */b)/* omg! */{/*}}*/},  
  
  // formatting -- sardonic  
  function  (  A,  b  
,c  ,d  
  )  
  {  
  },  
  
  // by reference  
  this.jQuery || function (a,b){return new e.fn.init(a,b,h)},
  $args,  
  
  // inadvertent non-function values  
  null,  
  Object  
].map(function(f) {
    var abbr = (f + '').replace(/\n/g, '\\n').replace(/\s+|[{]+$/g, ' ').split("{", 1)[0] + "...";
    return "    '" + abbr + "' // returns " + JSON.stringify($args(f));
  }).join("\n") + "\n"); // output for copy and paste as a markdown snippet
<pre id='console_info'></pre>

33
humbletim

Une solution moins sujette aux erreurs d'espaces et de commentaires serait:

var fn = function(/* whoa) */ hi, you){};

fn.toString()
  .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
  .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
  .split(/,/)

["hi", "you"]
21
bubersson

Beaucoup de réponses ici utilisent des expressions rationnelles, c'est correct, mais cela ne gère pas très bien les nouveaux ajouts au langage (comme les fonctions de flèches et les classes). Il convient également de noter que si vous utilisez l’une de ces fonctions sur du code simplifié, cela va aller ????. Il utilisera quel que soit le nom minifié. Angular résout ce problème en vous permettant de transmettre un tableau de chaînes ordonné qui correspond à l'ordre des arguments lors de leur enregistrement dans le conteneur DI. Ainsi de suite avec la solution: 

var esprima = require('esprima');
var _ = require('lodash');

const parseFunctionArguments = (func) => {
    // allows us to access properties that may or may not exist without throwing 
    // TypeError: Cannot set property 'x' of undefined
    const maybe = (x) => (x || {});

    // handle conversion to string and then to JSON AST
    const functionAsString = func.toString();
    const tree = esprima.parse(functionAsString);
    console.log(JSON.stringify(tree, null, 4))
    // We need to figure out where the main params are. Stupid arrow functions ????
    const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
    const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params 
                                     : maybe(_.first(tree.body)).params;

    // extract out the param names from the JSON AST
    return _.map(params, 'name');
};

Ceci gère le problème d'analyse d'origine et quelques types de fonctions supplémentaires (par exemple, les fonctions de flèche). Voici une idée de ce qu'il peut et ne peut pas gérer tel quel: 

// I usually use mocha as the test runner and chai as the assertion library
describe('Extracts argument names from function signature. ????', () => {
    const test = (func) => {
        const expectation = ['it', 'parses', 'me'];
        const result = parseFunctionArguments(toBeParsed);
        result.should.equal(expectation);
    } 

    it('Parses a function declaration.', () => {
        function toBeParsed(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses a functional expression.', () => {
        const toBeParsed = function(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses an arrow function', () => {
        const toBeParsed = (it, parses, me) => {};
        test(toBeParsed);
    });

    // ================= cases not currently handled ========================

    // It blows up on this type of messing. TBH if you do this it deserves to 
    // fail ???? On a tech note the params are pulled down in the function similar 
    // to how destructuring is handled by the ast.
    it('Parses complex default params', () => {
        function toBeParsed(it=4*(5/3), parses, me) {}
        test(toBeParsed);
    });

    // This passes back ['_ref'] as the params of the function. The _ref is a 
    // pointer to an VariableDeclarator where the ✨???? happens.
    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ({it, parses, me}){}
        test(toBeParsed);
    });

    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ([it, parses, me]){}
        test(toBeParsed);
    });

    // Classes while similar from an end result point of view to function
    // declarations are handled completely differently in the JS AST. 
    it('Parses a class constructor when passed through', () => {
        class ToBeParsed {
            constructor(it, parses, me) {}
        }
        test(ToBeParsed);
    });
});

Selon ce que vous souhaitez utiliser pour ES6 Proxies et la déstructuration peut être votre meilleur choix. Par exemple, si vous souhaitez l’utiliser pour l’injection de dépendance (en utilisant les noms des paramètres), procédez comme suit: 

class GuiceJs {
    constructor() {
        this.modules = {}
    }
    resolve(name) {
        return this.getInjector()(this.modules[name]);
    }
    addModule(name, module) {
        this.modules[name] = module;
    }
    getInjector() {
        var container = this;

        return (klass) => {
            console.log(klass);
            var paramParser = new Proxy({}, {
                // The `get` handler is invoked whenever a get-call for
                // `injector.*` is made. We make a call to an external service
                // to actually hand back in the configured service. The proxy
                // allows us to bypass parsing the function params using
                // taditional regex or even the newer parser.
                get: (target, name) => container.resolve(name),

                // You shouldn't be able to set values on the injector.
                set: (target, name, value) => {
                    throw new Error(`Don't try to set ${name}! ????`);
                }
            })
            return new klass(paramParser);
        }
    }
}

Ce n'est pas le résolveur le plus avancé du marché, mais il donne une idée de la façon dont vous pouvez utiliser un proxy pour le gérer si vous souhaitez utiliser un analyseur de syntaxe pour une simple DI. Il y a cependant une légère mise en garde dans cette approche. Nous devons utiliser des assignations de déstructuration au lieu des params normaux. Lorsque nous transmettons le proxy injecteur, la déstructuration est identique à l'appel du getter sur l'objet. 

class App {
   constructor({Tweeter, timeline}) {
        this.Tweeter = Tweeter;
        this.timeline = timeline;
    }
}

class HttpClient {}

class TwitterApi {
    constructor({client}) {
        this.client = client;
    }
}

class Timeline {
    constructor({api}) {
        this.api = api;
    }
}

class Tweeter {
    constructor({api}) {
        this.api = api;
    }
}

// Ok so now for the business end of the injector!
const di = new GuiceJs();

di.addModule('client', HttpClient);
di.addModule('api', TwitterApi);
di.addModule('Tweeter', Tweeter);
di.addModule('timeline', Timeline);
di.addModule('app', App);

var app = di.resolve('app');
console.log(JSON.stringify(app, null, 4));

Cela génère les éléments suivants:

{
    "Tweeter": {
        "api": {
            "client": {}
        }
    },
    "timeline": {
        "api": {
            "client": {}
        }
    }
}

Son câblé toute l'application. Le mieux, c'est que l'application est facile à tester (vous pouvez simplement instancier chaque classe et la passer en mock/stubs/etc.). De plus, si vous devez échanger des implémentations, vous pouvez le faire depuis un seul endroit. Tout cela est possible grâce aux objets du proxy JS. 

Remarque: Il y a beaucoup de travail à faire avant de pouvoir utiliser la production, mais cela donne une idée de ce à quoi cela pourrait ressembler.

La réponse est un peu tardive, mais cela peut aider d'autres personnes qui pensent à la même chose. ????

15
James Drew

Je sais que c'est une vieille question, mais les débutants l'ont copypastisé comme si c'était une bonne pratique dans n'importe quel code. La plupart du temps, le fait d'analyser la représentation sous forme de chaîne d'une fonction pour utiliser ses noms de paramètres masque une faille dans la logique du code.

Les paramètres d'une fonction sont en réalité stockés dans un objet de type tableau appelé arguments, où le premier argument est arguments[0], le second est arguments[1] et ainsi de suite. L'écriture des noms de paramètres entre parenthèses peut être vue comme une syntaxe abrégée. Ce:

function doSomething(foo, bar) {
    console.log("does something");
}

...est le même que:

function doSomething() {
    var foo = arguments[0];
    var bar = arguments[1];

    console.log("does something");
}

Les variables elles-mêmes sont stockées dans la portée de la fonction et non en tant que propriétés dans un objet. Il n’existe aucun moyen de récupérer le nom du paramètre via le code car il s’agit simplement d’un symbole représentant la variable en langage humain.

J'ai toujours considéré la représentation sous forme de chaîne d'une fonction comme un outil à des fins de débogage, notamment à cause de cet objet ressemblant à un tableau arguments. Vous n'êtes pas obligé de donner des noms aux arguments en premier lieu. Si vous essayez d’analyser une fonction stringifiée, elle ne vous indiquera pas réellement quels paramètres supplémentaires non nommés peuvent être nécessaires.

Voici une situation encore pire et plus commune. Si une fonction a plus de 3 ou 4 arguments, il peut être logique de lui passer un objet, ce qui facilite le travail.

function saySomething(obj) {
  if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}

saySomething({sender: "user123", message: "Hello world"});

Dans ce cas, la fonction elle-même sera capable de lire à travers l'objet qu'elle reçoit, de rechercher ses propriétés et d'obtenir leurs noms et leurs valeurs, mais essayer d'analyser la représentation sous forme de chaîne de la fonction ne vous donnerait que "obj" pour les paramètres ce qui n'est pas du tout utile.

11
Jacque Goupil

Comme JavaScript est un langage de script, j’estime que son introspection devrait permettre d’obtenir des noms de paramètres de fonction. L'utilisation de cette fonctionnalité est une violation des principes de base, j'ai donc décidé d'explorer davantage la question.

Cela m'a amené à cette question mais pas de solutions intégrées. Ce qui m'a amené à cette réponse qui explique que arguments est seulement obsolète en dehors de la fonction, nous ne pouvons donc plus utiliser myFunction.arguments ou nous obtenons:

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

Il est temps de se retrousser les manches et de se mettre au travail:

⭐ La récupération des paramètres de fonction nécessite un analyseur, car des expressions complexes telles que 4*(5/3) peuvent être utilisées comme valeurs par défaut. So La réponse de Gaafar ou La réponse de James Drew sont jusqu'à présent les meilleures approches.

J'ai essayé les analyseurs babylon et esprima mais malheureusement, ils ne peuvent pas analyser les fonctions anonymes autonomes, comme indiqué dans la réponse de Mateusz Charytoniuk . J'ai trouvé une autre solution de contournement en entourant le code entre parenthèses afin de ne pas changer la logique:

const ast = parser.parse("(\n" + func.toString() + "\n)")

Les nouvelles lignes évitent les problèmes avec // (commentaires sur une seule ligne).

⭐ Si un analyseur n'est pas disponible, la meilleure option consiste à utiliser une technique éprouvée telle que les expressions régulières d'injecteur de dépendance d'Angular.js. J'ai combiné une version fonctionnelle de la réponse de Lambder avec la réponse de humbletim et j'ai ajouté un booléen ARROW facultatif permettant de contrôler si les expressions régulières autorisaient les fonctions ES6 fat arrow.


Voici deux solutions que j'ai mises ensemble. Notez que ceux-ci n'ont pas de logique pour détecter si une fonction a une syntaxe valide, ils extraient uniquement les arguments. Ceci est généralement correct puisque nous passons généralement les fonctions analysées à getArguments(), de sorte que leur syntaxe est déjà valide.

Je vais essayer de gérer ces solutions du mieux que je peux, mais sans les efforts des mainteneurs de JavaScript, cela restera un problème ouvert.

Version Node.js (non exécutable jusqu'à ce que StackOverflow prenne en charge Node.js):

const parserName = 'babylon';
// const parserName = 'esprima';
const parser = require(parserName);

function getArguments(func) {
    const maybe = function (x) {
        return x || {}; // optionals support
    }

    try {
        const ast = parser.parse("(\n" + func.toString() + "\n)");
        const program = parserName == 'babylon' ? ast.program : ast;

        return program
            .body[0]
            .expression
            .params
            .map(function(node) {
                return node.name || maybe(node.left).name || '...' + maybe(node.argument).name;
            });
    } catch (e) {
        return []; // could also return null
    }
};

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Exemple de travail complet:

https://repl.it/repls/SandybrownPhonyAngles

Version du navigateur (notez qu'il s'arrête à la première valeur par défaut complexe):

function getArguments(func) {
    const ARROW = true;
    const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
    const FUNC_ARG_SPLIT = /,/;
    const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
    const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    return ((func || '').toString().replace(STRIP_COMMENTS, '').match(FUNC_ARGS) || ['', '', ''])[2]
        .split(FUNC_ARG_SPLIT)
        .map(function(arg) {
            return arg.replace(FUNC_ARG, function(all, underscore, name) {
                return name.split('=')[0].trim();
            });
        })
        .filter(String);
}

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

logArgs(function(a, b, c) {});
logArgs(function() {});
logArgs(function named(a, b, c) {});
logArgs(function(a /* = 1 */, b /* = true */) {});
logArgs(function fprintf(handle, fmt /*, ...*/) {});
logArgs(function(a, b = 1, c) {});
logArgs(function(a = 4 * (5 / 3), b) {});
// logArgs(function (a, // single-line comment xjunk) {});
// logArgs(function (a /* fooled you {});
// logArgs(function (a /* function() yes */, \n /* no, */b)/* omg! */ {});
// logArgs(function ( A, b \n,c ,d \n ) \n {});
logArgs(function(a, b) {});
logArgs(function $args(func) {});
logArgs(null);
logArgs(function Object() {});

console.log('');
console.log('////////// STRINGS //////////');

logArgs('function (a,b,c) {}');
logArgs('function () {}');
logArgs('function named(a, b, c) {}');
logArgs('function (a /* = 1 */, b /* = true */) {}');
logArgs('function fprintf(handle, fmt /*, ...*/) {}');
logArgs('function( a, b = 1, c ) {}');
logArgs('function (a=4*(5/3), b) {}');
logArgs('function (a, // single-line comment xjunk) {}');
logArgs('function (a /* fooled you {}');
logArgs('function (a /* function() yes */, \n /* no, */b)/* omg! */ {}');
logArgs('function ( A, b \n,c ,d \n ) \n {}');
logArgs('function (a,b) {}');
logArgs('function $args(func) {}');
logArgs('null');
logArgs('function Object() {}');

Exemple de travail complet:

https://repl.it/repls/StupendousShowyOffices

8
Zack Morris
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=> ["a", "b", "c"]

7
Will

J'ai déjà essayé de le faire auparavant, mais je n'ai jamais trouvé de moyen pratique de le faire. J'ai fini par passer à la place d'un objet puis à le parcourir en boucle.

//define like
function test(args) {
    for(var item in args) {
        alert(item);
        alert(args[item]);
    }
}

//then used like
test({
    name:"Joe",
    age:40,
    admin:bool
});
6
Hugoware

Vous pouvez également utiliser l'analyseur "esprima" pour éviter de nombreux problèmes avec les commentaires, les espaces et autres éléments de la liste de paramètres.

function getParameters(yourFunction) {
    var i,
        // safetyValve is necessary, because sole "function () {...}"
        // is not a valid syntax
        parsed = esprima.parse("safetyValve = " + yourFunction.toString()),
        params = parsed.body[0].expression.right.params,
        ret = [];

    for (i = 0; i < params.length; i += 1) {
        // Handle default params. Exe: function defaults(a = 0,b = 2,c = 3){}
        if (params[i].type == 'AssignmentPattern') {
            ret.Push(params[i].left.name)
        } else {
            ret.Push(params[i].name);
        }
    }

    return ret;
}

Cela fonctionne même avec un code comme celui-ci:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]
5
Mateusz Charytoniuk

Je ne sais pas si cette solution convient à votre problème, mais elle vous permet de redéfinir la fonction de votre choix, sans avoir à changer de code. Les appels existants utiliseront des paramètres positionnés, alors que la fonction implémentée peut utiliser des "paramètres nommés" (un paramètre de hachage).

Je pensais que vous alliez quand même modifier les définitions de fonctions existantes, alors pourquoi ne pas utiliser une fonction fabrique qui donne exactement ce que vous voulez:

<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var withNamedParams = function(params, lambda) {
    return function() {
        var named = {};
        var max   = arguments.length;

        for (var i=0; i<max; i++) {
            named[params[i]] = arguments[i];
        }

        return lambda(named);
    };
};

var foo = withNamedParams(["a", "b", "c"], function(params) {
    for (var param in params) {
        alert(param + ": " + params[param]);
    }
});

foo(1, 2, 3);
</script>
</head>
<body>

</body>
</html>

J'espère que ça aide.

4
Ionuț G. Stan

Je ne sais pas comment obtenir une liste des paramètres, mais vous pouvez le faire pour en connaître le nombre.

alert(doSomething.length);
2
Ólafur Waage

Prenant le answer de @ jack-allan, j’ai légèrement modifié la fonction pour autoriser les propriétés par défaut de l’ES6, telles que:

function( a, b = 1, c ){};

toujours retourner [ 'a', 'b' ]

/**
 * Get the keys of the paramaters of a function.
 *
 * @param {function} method  Function to get parameter keys for
 * @return {array}
 */
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
function getFunctionParameters ( func ) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '');
    var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
    var result = argsList.match( ARGUMENT_NAMES );

    if(result === null) {
        return [];
    }
    else {
        var stripped = [];
        for ( var i = 0; i < result.length; i++  ) {
            stripped.Push( result[i].replace(/[\s,]/g, '') );
        }
        return stripped;
    }
}
2
thelastshadow

Ce paquet utilise la refonte pour créer un AST, puis les noms des paramètres sont rassemblés, ce qui lui permet de prendre en charge la correspondance de modèle, les arguments par défaut, les fonctions de flèche et d'autres fonctionnalités de l'ES6.

https://www.npmjs.com/package/es-arguments

1
user3436035

La réponse à cette question nécessite 3 étapes:

  1. Pour obtenir les valeurs des paramètres réels passés à la fonction (appelons-le argValues). Ceci est simple car il sera disponible sous la forme arguments dans la fonction.
  2. Pour obtenir les noms de paramètre à partir de la signature de la fonction (appelons-la argNames). Ce n'est pas aussi facile et nécessite l'analyse de la fonction. Au lieu de vous charger vous-même de la regex complexe et de vous inquiéter des cas Edge (paramètres par défaut, commentaires, ...), vous pouvez utiliser une bibliothèque comme babylon qui analysera la fonction dans un arbre de syntaxe abstraite à partir duquel vous pourrez obtenir les noms des paramètres.
  3. La dernière étape consiste à réunir les deux tableaux en un tableau qui porte le nom et la valeur de tous les paramètres.

Le code sera comme ça

const babylon = require("babylon")
function doSomething(a, b, c) {
    // get the values of passed argumenst
    const argValues = arguments

    // get the names of the arguments by parsing the function
    const ast = babylon.parse(doSomething.toString())
    const argNames =  ast.program.body[0].params.map(node => node.name)

    // join the 2 arrays, by looping over the longest of 2 arrays
    const maxLen = Math.max(argNames.length, argValues.length)
    const args = []
    for (i = 0; i < maxLen; i++) { 
       args.Push({name: argNames[i], value: argValues[i]})
    }
    console.log(args)

    // implement the actual function here
}

doSomething(1, 2, 3, 4)

et l'objet enregistré sera 

[
  {
    "name": "a",
    "value": 1
  },
  {
    "name": "c",
    "value": 3
  },
  {
    "value": 4
  }
]

Et voici un exemple de travail https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

1
Gaafar

J'ai modifié la version tirée de AngularJS qui implémente un mécanisme d'injection de dépendance pour fonctionner sans Angular. J'ai également mis à jour la regex STRIP_COMMENTS pour qu'elle fonctionne avec ECMA6. Elle prend donc en charge des éléments comme les valeurs par défaut dans la signature.

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

function annotate(fn) {
  var $inject,
    fnText,
    argDecl,
    last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
        arg.replace(FN_ARG, function(all, underscore, name) {
          $inject.Push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else {
    throw Error("not a function")
  }
  return $inject;
}

console.log("function(a, b)",annotate(function(a, b) {
  console.log(a, b, c, d)
}))
console.log("function(a, b = 0, /*c,*/ d)",annotate(function(a, b = 0, /*c,*/ d) {
  console.log(a, b, c, d)
}))
annotate({})

1
loretoparisi

Comment je le fais typiquement:

function name(arg1, arg2){
    var args = arguments; // array: [arg1, arg2]
    var objecArgOne = args[0].one;
}
name({one: "1", two: "2"}, "string");

Vous pouvez même référencer les arguments par le nom des fonctions comme: 

name.arguments;

J'espère que cela t'aides!

1
Cody
//See this:


// global var, naming bB
var bB = 5;

//  Dependency Injection cokntroller
var a = function(str, fn) {
  //stringify function body
  var fnStr = fn.toString();

  // Key: get form args to string
  var args = fnStr.match(/function\s*\((.*?)\)/);
  // 
  console.log(args);
  // if the form arg is 'bB', then exec it, otherwise, do nothing
  for (var i = 0; i < args.length; i++) {
    if(args[i] == 'bB') {
      fn(bB);
    }
  }
}
// will do nothing
a('sdfdfdfs,', function(some){
alert(some)
});
// will alert 5

a('sdfdsdsfdfsdfdsf,', function(bB){
alert(bB)
});

// see, this shows you how to get function args in string
1
Paul Lan
function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;

    while (tokens = /\s*([^,]+)/g.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}
1
user3018868

Pour ce faire, utilisez un analyseur JS. Voici un exemple utilisant acorn .

const acorn = require('acorn');    

function f(a, b, c) {
   // ...
}

const argNames = acorn.parse(f).body[0].params.map(x => x.name);
console.log(argNames);  // Output: [ 'a', 'b', 'c' ]

Le code trouve ici les noms des trois paramètres (formels) de la fonction f. Pour ce faire, il insère f dans acorn.parse().

1
Itay Maman

Wow tellement de réponses déjà .. Je suis à peu près sûr que cela soit enterré. Même si j'ai pensé que cela pourrait être utile pour certains.

Je n'étais pas entièrement satisfait des réponses choisies car dans ES6, cela ne fonctionnait pas bien avec les valeurs par défaut. Et il ne fournit pas non plus les informations de valeur par défaut. Je voulais aussi une fonction légère qui ne dépend pas d'une bibliothèque externe.

Cette fonction est très utile pour le débogage, par exemple: journalisation appelée fonction avec ses paramètres, valeurs par défaut et arguments.

Hier, j'ai passé un peu de temps là-dessus, à trouver le bon RegExp pour résoudre ce problème, et voici ce que j'ai proposé. Cela fonctionne très bien et je suis très heureux du résultat:

const REGEX_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const REGEX_FUNCTION_PARAMS = /(?:\s*(?:function\s*[^(]*)?\s*)((?:[^'"]|(?:(?:(['"])(?:(?:.*?[^\\]\2)|\2))))*?)\s*(?=(?:=>)|{)/m
const REGEX_PARAMETERS_VALUES = /\s*(\w+)\s*(?:=\s*((?:(?:(['"])(?:\3|(?:.*?[^\\]\3)))((\s*\+\s*)(?:(?:(['"])(?:\6|(?:.*?[^\\]\6)))|(?:[\w$]*)))*)|.*?))?\s*(?:,|$)/gm

/**
 * Retrieve a function's parameter names and default values
 * Notes:
 *  - parameters with default values will not show up in transpiler code (Babel) because the parameter is removed from the function.
 *  - does NOT support inline arrow functions as default values
 *      to clarify: ( name = "string", add = defaultAddFunction )   - is ok
 *                  ( name = "string", add = ( a )=> a + 1 )        - is NOT ok
 *  - does NOT support default string value that are appended with a non-standard ( Word characters or $ ) variable name
 *      to clarify: ( name = "string" + b )         - is ok
 *                  ( name = "string" + $b )        - is ok
 *                  ( name = "string" + b + "!" )   - is ok
 *                  ( name = "string" + λ )         - is NOT ok
 * @param {function} func
 * @returns {Array} - An array of the given function's parameter [key, default value] pairs.
 */
function getParams(func) {

  let functionAsString = func.toString()
  let params = []
  let match
  functionAsString = functionAsString.replace(REGEX_COMMENTS, '')
  functionAsString = functionAsString.match(REGEX_FUNCTION_PARAMS)[1]
  if (functionAsString.charAt(0) === '(') functionAsString = functionAsString.slice(1, -1)
  while (match = REGEX_PARAMETERS_VALUES.exec(functionAsString)) params.Push([match[1], match[2]])
  return params

}



// Lets run some tests!

var defaultName = 'some name'

function test1(param1, param2, param3) { return (param1) => param1 + param2 + param3 }
function test2(param1, param2 = 4 * (5 / 3), param3) {}
function test3(param1, param2 = "/root/" + defaultName + ".jpeg", param3) {}
function test4(param1, param2 = (a) => a + 1) {}

console.log(getParams(test1)) 
console.log(getParams(test2))
console.log(getParams(test3))
console.log(getParams(test4))

// [ [ 'param1', undefined ], [ 'param2', undefined ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '4 * (5 / 3)' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '"/root/" + defaultName + ".jpeg"' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '( a' ] ]
// --> This last one fails because of the inlined arrow function!


var arrowTest1 = (a = 1) => a + 4
var arrowTest2 = a => b => a + b
var arrowTest3 = (param1 = "/" + defaultName) => { return param1 + '...' }
var arrowTest4 = (param1 = "/" + defaultName, param2 = 4, param3 = null) => { () => param3 ? param3 : param2 }

console.log(getParams(arrowTest1))
console.log(getParams(arrowTest2))
console.log(getParams(arrowTest3))
console.log(getParams(arrowTest4))

// [ [ 'a', '1' ] ]
// [ [ 'a', undefined ] ]
// [ [ 'param1', '"/" + defaultName' ] ]
// [ [ 'param1', '"/" + defaultName' ], [ 'param2', '4' ], [ 'param3', 'null' ] ]


console.log(getParams((param1) => param1 + 1))
console.log(getParams((param1 = 'default') => { return param1 + '.jpeg' }))

// [ [ 'param1', undefined ] ]
// [ [ 'param1', '\'default\'' ] ]

Comme vous pouvez le constater, certains noms de paramètres disparaissent car le transpiler de Babel les supprime de la fonction. Si vous exécutez ceci dans le dernier NodeJS, cela fonctionnera comme prévu (les résultats commentés proviennent de NodeJS).

Une autre remarque, comme indiqué dans le commentaire, est que cela ne fonctionne pas avec les fonctions fléchées en ligne comme valeur par défaut. Cela rend tout simplement compliqué d'extraire les valeurs à l'aide d'un RegExp.

S'il vous plaît laissez-moi savoir si cela vous a été utile! J'aimerais entendre des commentaires!

1
SnailCrusher

Remarque: si vous souhaitez utiliser la déstructuration des paramètres ES6 avec la solution supérieure, ajoutez la ligne suivante.

if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)
0
IanLancaster

Je vais vous donner un court exemple ci-dessous:

function test(arg1,arg2){
    var funcStr = test.toString()
    var leftIndex = funcStr.indexOf('(');
    var rightIndex = funcStr.indexOf(')');
    var paramStr = funcStr.substr(leftIndex+1,rightIndex-leftIndex-1);
    var params = paramStr.split(',');
    for(param of params){
        console.log(param);   // arg1,arg2
    }
}

test();
0
myzhou

Vous pouvez accéder aux valeurs d'argument transmises à une fonction à l'aide de la propriété "arguments".

    function doSomething()
    {
        var args = doSomething.arguments;
        var numArgs = args.length;
        for(var i = 0 ; i < numArgs ; i++)
        {
            console.log("arg " + (i+1) + " = " + args[i]);  
                    //console.log works with firefox + firebug
                    // you can use an alert to check in other browsers
        }
    }

    doSomething(1, '2', {A:2}, [1,2,3]);    
0
letronje

C'est assez facile.

Au premier, il y a un arguments.callee obsolète - une référence à la fonction appelée . Au second, si vous avez une référence à votre fonction, vous pouvez facilement obtenir leur représentation textuelle . Au troisième si vous appelez votre fonction en tant que constructeur vous pouvez également avoir un lien via yourObject.constructor . NB: La première solution est obsolète. Si vous ne pouvez pas ne pas l'utiliser, vous devez également penser à l'architecture de votre application . Si vous n'avez pas besoin de la variable exacte Les noms utilisent simplement une variable interne arguments de la fonction sans magie.

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

Tous vont appeler toString et les remplacer par re afin que nous puissions créer un assistant:

// getting names of declared parameters
var getFunctionParams = function (func) {
    return String(func).replace(/[^\(]+\(([^\)]*)\).*/m, '$1');
}

Quelques exemples:

// Solution 1. deprecated! don't use it!
var myPrivateFunction = function SomeFuncName (foo, bar, buz) {
    console.log(getFunctionParams(arguments.callee));
};
myPrivateFunction (1, 2);

// Solution 2.
var myFunction = function someFunc (foo, bar, buz) {
    // some code
};
var params = getFunctionParams(myFunction);
console.log(params);

// Solution 3.
var cls = function SuperKewlClass (foo, bar, buz) {
    // some code
};
var inst = new cls();
var params = getFunctionParams(inst.constructor);
console.log(params);

Profitez avec JS!

UPD: En fait, Jack Allan a reçu une solution un peu meilleure. GJ Jack!

0
Alex Yaroshevich

Quelle que soit la solution, il ne faut pas qu'il se casse avec des fonctions étranges, dont toString() est identique à celui-ci:

function  (  A,  b
,c      ,d
){}

screenshot from console

Aussi, pourquoi utiliser des expressions régulières complexes? Cela peut être fait comme:

function getArguments(f) {
    return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}

Cela fonctionne partout avec toutes les fonctions, et le seul regex est la suppression d'espaces qui ne traite même pas la chaîne entière en raison de l'astuce .split.

0
Camilo Martin

Voici un moyen:

// Utility function to extract arg name-value pairs
function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;
    var argRe = /\s*([^,]+)/g;

    while (tokens = argRe.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}

// Test subject
function add(number1, number2) {
    var args = getArgs(arguments);
    console.log(args); // ({ number1: 3, number2: 4 })
}

// Invoke test subject
add(3, 4);

Remarque: cela ne fonctionne que sur les navigateurs prenant en charge arguments.callee.

0
Ates Goral

Ok, donc une vieille question avec beaucoup de réponses adéquates… .. voici mon offre qui n'utilise pas de regex, à l'exception de la tâche subalterne consistant à supprimer les espaces. (Je devrais noter que la fonction "strips_comments" les espace plutôt que de les supprimer physiquement. C'est parce que je l'utilise ailleurs et que, pour diverses raisons, les emplacements des jetons de non-commentaire d'origine doivent rester intacts)

C'est un bloc de code assez long car ce collage inclut un mini-framework de test.

    function do_tests(func) {

    if (typeof func !== 'function') return true;
    switch (typeof func.tests) {
        case 'undefined' : return true;
        case 'object'    : 
            for (var k in func.tests) {

                var test = func.tests[k];
                if (typeof test==='function') {
                    var result = test(func);
                    if (result===false) {
                        console.log(test.name,'for',func.name,'failed');
                        return false;
                    }
                }

            }
            return true;
        case 'function'  : 
            return func.tests(func);
    }
    return true;
} 
function strip_comments(src) {

    var spaces=(s)=>{
        switch (s) {
            case 0 : return '';
            case 1 : return ' ';
            case 2 : return '  ';
        default : 
            return Array(s+1).join(' ');
        }
    };

    var c1 = src.indexOf ('/*'),
        c2 = src.indexOf ('//'),
        eol;

    var out = "";

    var killc2 = () => {
                out += src.substr(0,c2);
                eol =  src.indexOf('\n',c2);
                if (eol>=0) {
                    src = spaces(eol-c2)+'\n'+src.substr(eol+1);
                } else {
                    src = spaces(src.length-c2);
                    return true;
                }

             return false;
         };

    while ((c1>=0) || (c2>=0)) {
         if (c1>=0) {
             // c1 is a hit
             if ( (c1<c2) || (c2<0) )  {
                 // and it beats c2
                 out += src.substr(0,c1);
                 eol = src.indexOf('*/',c1+2);
                 if (eol>=0) {
                      src = spaces((eol-c1)+2)+src.substr(eol+2);
                 } else {
                      src = spaces(src.length-c1);
                      break;
                 }
             } else {

                 if (c2 >=0) {
                     // c2 is a hit and it beats c1
                     if (killc2()) break;
                 }
             }
         } else {
             if (c2>=0) {
                // c2 is a hit, c1 is a miss.
                if (killc2()) break;  
             } else {
                 // both c1 & c2 are a miss
                 break;
             }
         }

         c1 = src.indexOf ('/*');
         c2 = src.indexOf ('//');   
        }

    return out + src;
}

function function_args(fn) {
    var src = strip_comments(fn.toString());
    var names=src.split(')')[0].replace(/\s/g,'').split('(')[1].split(',');
    return names;
}

function_args.tests = [

     function test1 () {

            function/*al programmers will sometimes*/strip_comments_tester/* because some comments are annoying*/(
            /*see this---(((*/ src//)) it's an annoying comment does not help anyone understand if the 
            ,code,//really does
            /**/sucks ,much /*?*/)/*who would put "comment\" about a function like (this) { comment } here?*/{

            }


        var data = function_args(strip_comments_tester);

        return ( (data.length==4) &&
                 (data[0]=='src') &&
                 (data[1]=='code') &&
                 (data[2]=='sucks') &&
                 (data[3]=='much')  );

    }

];
do_tests(function_args);
0
unsynchronized