Comment obtenir dynamiquement des noms / valeurs de paramètres de fonction?


301

Existe-t-il un moyen d'obtenir dynamiquement les noms des paramètres de fonction d'une fonction?

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 puis-je obtenir une liste des noms de paramètres et de leurs valeurs dans un tableau à l'intérieur de la fonction?


Merci à tout le monde. Après avoir cherché, j'ai trouvé la solution sur SO: stackoverflow.com/questions/914968/… Il utilise une expression régulière pour obtenir le nom du paramètre. Ce n'est probablement pas la meilleure solution, mais cela fonctionne pour moi.
vikasde

8
allons de l'avant et marquons cela comme répondu copain. aucune nouvelle réponse ne vient.
Matthew Graves

function doSomething(...args) { /*use args*/}
caub

Réponses:


323

La fonction suivante renverra un tableau des noms de paramètres de toute fonction transmise.

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 hack rapide 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, car il y a des choses qui vont faire 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 n'a 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, on peut utiliser les éléments suivants à la place:

var args = Array.slice(arguments);

12
Notez que cette solution peut échouer à cause des commentaires et des espaces - par exemple: var fn = function(a /* fooled you)*/,b){};entraînera ["a", "/*", "fooled", "you"]
bubersson

1
J'ai modifié la fonction pour retourner un tableau vide (au lieu de null) quand il n'y a pas d'arguments
BT

2
Il y a un coût à compiler une regex, donc vous voulez éviter de compiler des regex complexes plus d'une fois. C'est pourquoi cela se fait en dehors de la fonction
Jack Allan

2
CORRECTION: J'allais modifier l'expression régulière avec un modificateur / s que perl autorise ainsi '.' peut également correspondre à la nouvelle ligne. Ceci est nécessaire pour les commentaires sur plusieurs lignes à l'intérieur de / * * /. Il s'avère que l'expression régulière Javascript n'autorise pas le modificateur / s. Le regex d'origine utilisant [/ s / S] correspond aux caractères de nouvelle ligne. SOOO, veuillez ignorer le commentaire précédent.
tgoneil

1
@andes Notez que vous incluez la compilation regex dans vos tests. Ces opérations ne doivent être effectuées qu'une seule fois. Vos résultats peuvent être différents si vous déplacez la compilation d'expressions régulières à l'étape de configuration au lieu du test
Jack Allan

123

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

Et voici une explication 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 sa construction. L'injecteur de dépendances s'occupe également de créer toutes les dépendances transitives que le service peut avoir (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;
}

40
@apaidnerd avec le sang des démons et le frai de satan, apparemment. Regex?! Ce serait cool s'il y avait un système intégré dans JS, n'est-ce pas?
Aditya MP

6
@apaidnerd, c'est vrai! Juste pensé - comment diable est-ce que cela est mis en œuvre? En fait, j'ai pensé à utiliser functionName.toString () mais j'espérais quelque chose de plus élégant (et peut-être plus rapide)
sasha.sochka

2
@ sasha.sochka, est venu ici en se demandant exactement la même chose, après avoir réalisé qu'il n'y avait pas de moyen intégré pour obtenir les noms des paramètres avec javascript
Hart Simha

14
pour gagner du temps, vous pouvez obtenir cette fonction de via angulaire via annotate = angular.injector.$$annotate
Nick

3
J'ai littéralement cherché ce sujet sur Internet parce que j'étais curieux de savoir comment Angular l'a fait ... maintenant je sais et aussi j'en sais trop!
Steven Hunt

40

Voici une solution mise à jour qui tente de résoudre tous les cas marginaux 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 [""]
}  

Sortie de test abrégée (les cas 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 []


Cela se casse lorsque des commentaires d'une ligne sont présents. Essayez ceci: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham

4
Vous devriez probablement remplacer func + ''par Function.toString.call(func)pour vous défendre contre le cas où la fonction a une implémentation .toString () personnalisée.
Paul Go

1
grosses flèches =>.split(/\)[\{=]/, 1)[0]
Matt

1
cela divise les objets déstructurés (comme ({ a, b, c })) en tous les paramètres à l'intérieur de la déstructuration. pour garder les objets déstructurés intacts, changez le dernier .spliten: .split(/,(?![^{]*})/g)
Michael Auderer

1
Cela ne fonctionnera pas non plus lorsqu'il existe des valeurs de chaîne par défaut qui contiennent "//" ou "/ *"
skerit

23

Une solution moins sujette aux erreurs et aux commentaires serait:

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

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

["hi", "you"]

1
@AlexMills Une chose que j'ai remarquée, c'est que la spécification des fonctions fléchées dit qu'elles ne devraient pas être traitées comme des «fonctions». Cela signifie qu'il ne serait pas approprié que cela corresponde aux fonctions du tableau. Le «ceci» n'est pas défini de la même manière, et ils ne devraient pas non plus être appelés en tant que fonctions. C'est quelque chose que j'ai appris à la dure. ($ myService) => $ myService.doSomething () a l'air cool, mais c'est une mauvaise utilisation des fonctions de tableau.
Andrew T Finnell

20

Beaucoup de réponses ici utilisent des expressions rationnelles, c'est bien mais cela ne gère pas trop bien les nouveaux ajouts au langage (comme les fonctions fléchées et les classes). Il est également à noter que si vous utilisez l'une de ces fonctions sur du code minifié, cela ira 🔥. Il utilisera le nom minifié. Angular contourne cela en vous permettant de passer dans un tableau ordonné de chaînes 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');
};

Cela gère le problème d'analyse d'origine et quelques autres types de fonctions (par exemple les fonctions fléchées). 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 voulez l'utiliser pour les proxys ES6 et la déstructuration peut être votre meilleur pari. Par exemple, si vous souhaitez l'utiliser pour l'injection de dépendances (en utilisant les noms des paramètres), vous pouvez le faire 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é, mais il donne une idée de la façon dont vous pouvez utiliser un proxy pour le gérer si vous souhaitez utiliser l'analyseur args pour une DI simple. Il y a cependant une légère mise en garde dans cette approche. Nous devons utiliser des affectations de déstructuration au lieu de paramètres normaux. Lorsque nous passons dans le proxy injecteur, la déstructuration équivaut à appeler le 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é l'ensemble de l'application. Le meilleur est que l'application est facile à tester (vous pouvez simplement instancier chaque classe et passer dans des mocks / stubs / etc). De plus, si vous devez échanger des implémentations, vous pouvez le faire à partir d'un seul endroit. Tout cela est possible grâce aux objets JS Proxy.

Remarque: il y a beaucoup de travail qui devrait être fait pour cela avant qu'il ne soit prêt pour une utilisation en production, mais cela donne une idée de ce à quoi il ressemblerait.

C'est un peu tard dans la réponse, mais cela peut aider ceux qui pensent à la même chose. 👍


13

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

Les paramètres d'une fonction sont en fait 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 de noms de paramètres entre parenthèses peut être considérée 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, pas en tant que propriétés dans un objet. Il n'y a 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 de débogage, en particulier à cause de cela arguments objet de type tableau. 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 indique pas réellement les paramètres supplémentaires non nommés qu'elle pourrait prendre.

Voici une situation encore pire et plus courante. Si une fonction a plus de 3 ou 4 arguments, il peut être logique de lui passer un objet à la place, ce qui est plus facile à utiliser.

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 pourra lire l'objet qu'elle reçoit et chercher ses propriétés et obtenir à la fois leurs noms et leurs valeurs, mais essayer d'analyser la représentation sous forme de chaîne de la fonction ne vous donnera que "obj" pour les paramètres, ce qui n'est pas du tout utile.


1
Je pense que le cas pour quelque chose comme ça est généralement: le débogage / journalisation, une sorte de décorateur qui fait des choses géniales (terme technique 😁), ou la construction d'un cadre d'injection de dépendance pour que vos applications injectent automatiquement en fonction du nom de l'argument (c'est ainsi que l'angulaire travaux). Un autre cas d'utilisation vraiment intéressant est dans promisify-node (c'est une bibliothèque qui prend une fonction qui prend normalement un rappel puis la convertit en promesse). Ils l'utilisent pour trouver des noms communs pour les rappels (comme cb / callback / etc) et ensuite ils peuvent vérifier si la fonction est asynchrone ou synchronisée avant de l'encapsuler.
James Drew

Voir ce fichier pour leur analyseur . C'est un peu naïf mais ça gère la majorité des cas.
James Drew

Intéressant, je suis surpris qu'une telle bibliothèque ait retenu l'attention. Eh bien, il y a plusieurs problèmes ouverts concernant des situations problématiques comme celles que j'ai décrites. Comme je l'ai dit, si c'est à des fins de débogage, c'est bien, mais s'appuyer sur la conversion de chaînes de fonctions dans les environnements de production est beaucoup trop risqué.
Domino

Sur un sidenote "drôle", vous pouvez faire en sorte que toutes les fonctions agissent comme si elles étaient des fonctions intégrées anonymes en exécutant ceci:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino

1
D'accord, c'est un peu compliqué de gérer ça comme ça. L'utilisation la meilleure et la plus simple est l'injection de dépendance. Dans ce cas, vous possédez le code et pouvez gérer la dénomination et d'autres zones de code. Je pense que c'est là qu'il verrait le plus d'utilisation. Im utilisant actuellement esprima (au lieu de regex) et ES6 Proxy( piège constructeur et piège à appliquer ) et Reflectionpour gérer DI pour certains de mes modules. C'est assez solide.
James Drew

10

Étant donné que JavaScript est un langage de script, je pense que son introspection devrait prendre en charge l'obtention des noms de paramètres de fonction. Se lancer sur cette fonctionnalité est une violation des premiers principes, j'ai donc décidé d'explorer la question plus avant.

Cela m'a amené à cette question, mais pas de solutions intégrées. Ce qui m'a conduit à cette réponse qui explique quearguments n'est déprécié qu'en dehors de la fonction, donc on ne peut plus utiliser myFunction.argumentsou on obtient:

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 retrousser nos manches et de se mettre au travail:

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

J'ai essayé le babylon et esprima parseurs mais malheureusement ils ne peuvent analyser des fonctions anonymes autonomes, comme en pointe 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 suivante consiste à utiliser une technique éprouvée comme les expressions régulières de l'injecteur de dépendance d'Angular.js. J'ai combiné une version fonctionnelle de la réponse de Lambder avec Lambder humbletim et ajouté un ARROWbooléen facultatif pour contrôler si les fonctions de flèche de graisse ES6 sont autorisées par les expressions régulières.


Voici deux solutions que j'ai mises en place. 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 car nous passons généralement des fonctions analysées à getArguments()leur syntaxe est donc déjà valide.

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

Version Node.js (non exécutable tant que StackOverflow ne prend pas 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'elle 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


si je ne me trompe pas, dans votre version de navigateur, le premier conditionnel dans FUNC_ARGS fonctionnera à la fois pour les fonctions fléchées et traditionnelles, vous n'avez donc pas besoin de la deuxième partie et vous pouvez donc vous débarrasser de la dépendance à ARROW.
pwilcox

C'est bien! Je cherchais une solution comme celle-ci qui utilise un analyseur pour couvrir la syntaxe ES6. J'envisage de l'utiliser pour créer un matcher "implémente l'interface" car la simple utilisation de function.length a des limites avec les paramètres par défaut, et je voulais pouvoir affirmer les paramètres de repos.
cue8chalk

Il convient de souligner que le cinquième cas de test qui contient des parenthèses dans la valeur par défaut échoue actuellement. Je souhaite que mon regex-fu soit assez fort pour être réparé, désolé!
Dale Anderson

8

J'ai lu la plupart des réponses ici, et je voudrais ajouter mon one-liner.

new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)').exec(Function.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

ou

function getParameters(func) {
  return new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
}

ou pour une fonction monoligne dans ECMA6

var getParameters = func => new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');

__

Disons que vous avez une fonction

function foo(abc, def, ghi, jkl) {
  //code
}

Le code ci-dessous sera de retour "abc,def,ghi,jkl"

Ce code fonctionnera également avec la configuration d'une fonction que Camilo Martin a donnée:

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

Aussi avec le commentaire de Bubersson sur la réponse de Jack Allan :

function(a /* fooled you)*/,b){}

__

Explication

new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')

Cela crée une expression régulière avec le new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Je dois utiliser new RegExpcar j'injecte une variable (Function.name , le nom de la fonction ciblée) dans RegExp.

Exemple Si le nom de la fonction est "foo" ( function foo()), le RegExp le sera /foo\s*\((.*?)\)/.

Function.toString().replace(/\n/g, '')

Il convertit ensuite la fonction entière en chaîne et supprime tous les retours à la ligne. La suppression des sauts de ligne aide à la configuration des fonctions de Camilo Martin .

.exec(...)[1]

Telle est la RegExp.prototype.execfonction. Il correspond essentiellement à l'exposant régulier ( new RegExp()) dans la chaîne ( Function.toString()). Ensuite, le [1]retournera le premier groupe de capture trouvé dans l'exposant régulier ( (.*?)).

.replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

Cela supprimera tous les commentaires à l'intérieur /*et */, et supprimera tous les espaces.


Cela prend également en charge la lecture et la compréhension des fonctions de flèche ( =>), telles que f = (a, b) => void 0;, dans lesquellesFunction.toString() retournerait (a, b) => void 0au lieu des fonctions normales function f(a, b) { return void 0; }. L'expression régulière d'origine aurait jeté une erreur dans sa confusion, mais est maintenant prise en compte.

Le changement est passé de new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) ànew RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') ( /(?:Function\s*|^)\((.*?)\)/)


Si vous souhaitez transformer tous les paramètres en un tableau au lieu d'une chaîne séparée par des virgules, ajoutez simplement à la fin .split(',').


plutôt sympa. une ligne, gère les fonctions de flèche, pas de dépendances externes, et couvre plus de suffisamment de cas de bord, au moins pour mon utilisation prévue. si vous pouvez faire des hypothèses raisonnables sur les signatures de fonction que vous allez traiter, c'est tout ce dont vous aurez besoin. Merci!
charlie roberts

Ne fonctionne pas avec cette fonction simple flèche: f = (a, b) => void 0; sur getParameters(f)je reçoisTypeError: Cannot read property '1' of null
AT

@AT Je viens de mettre à jour la réponse pour corriger la prise en charge de votre problème
Jaketr00

Merci ... mais gardez à l' esprit que les parenthèses ne sont plus nécessaires, afin que vous puissiez faire des choses comme getParameters(a => b => c => d => a*b*c*d), qui , avec votre code donne encore que TypeError: Cannot read property '1' of null... alors que celui - ci fonctionne stackoverflow.com/a/29123804
AT

Ne fonctionne pas lorsque la fonction a des valeurs par défaut (rôle, nom = "bob") Le paramètre extrait est nom = "bob" au lieu du "nom" attendu
Dmitri

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

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


7

Vous pouvez également utiliser l'analyseur "esprima" pour éviter de nombreux problèmes avec les commentaires, les espaces et autres choses dans la liste des 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 du code comme celui-ci:

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

6

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

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

J'ai de nombreuses fonctions déjà prédéfinies qui sont appelées avec des paramètres standard au lieu d'un seul objet. Tout changer prendrait beaucoup de temps.
vikasde

2
Cette réponse n'est pas la réponse à la question d'origine qui a été définie avec précision. Il montre une solution à un problème complètement différent. La question d'origine se réfère à la technique qu'AngularJS utilise pour son injection de dépendance. Les noms d'argument sont significatifs car ils correspondent aux dépendances du module que DI fournit automatiquement.
Lambder

4

Je ne sais pas si cette solution convient à votre problème, mais elle vous permet de redéfinir la fonction que vous souhaitez, sans avoir à changer le code qui l'utilise. Les appels existants utiliseront des paramètres positionnés, tandis que l'implémentation de la fonction peut utiliser des "paramètres nommés" (un seul paramètre de hachage).

Je pensais que vous modifieriez de toute façon les définitions de fonctions existantes, alors pourquoi ne pas avoir une fonction d'usine qui fait 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

La bonne façon de procéder consiste à utiliser un analyseur JS. Voici un exemple utilisant du gland .

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. Il le fait par l' alimentation fen acorn.parse().


qu'en est-il des valeurs?
eran otzap

2

Je ne sais pas comment obtenir une liste des paramètres mais vous pouvez le faire pour obtenir combien il attend.

alert(doSomething.length);

function gotcha (a, b = false, c) {}; alert(gotcha.length)
balupton

2

En prenant la réponse de @ jack-allan, j'ai légèrement modifié la fonction pour autoriser les propriétés par défaut d'ES6 telles que:

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

revenir encore [ '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;
    }
}

1
Merci, le vôtre est le seul dans ce fil qui a fonctionné pour moi.
AU

1

Comment je le fais généralement:

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!


4
Et où sont les noms des paramètres de fonction?
Andrej

Ah ... Vous voulez dire que vous le voulez sous forme de hachage? Comme si: var args = name.arguments; console.log('I WANNa SEE', args);sortir quelque chose comme "{arg1: {...}, arg2: 'string'}"? Cela pourrait éclaircir les choses: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Les arguments de fonction sont un objet de type «tableau», ayant des indices énumérables. Je ne pense pas qu'un mappage de hachage soit possible, mais vous pouvez simplement passer un littéral objet dans lequel est une bonne pratique si vous avez de toute façon plus de 4 paramètres.
Cody

1
//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

La réponse à cela nécessite 3 étapes:

  1. Pour obtenir les valeurs des paramètres réels passés à la fonction (appelons-la argValues). C'est simple car il sera disponible enarguments dans la fonction.
  2. Pour obtenir les noms des paramètres à 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 faire vous-même le regex complexe et de vous soucier des cas marginaux (paramètres par défaut, commentaires, ...), vous pouvez utiliser une bibliothèque comme babylon qui analysera la fonction dans une arborescence de syntaxe abstraite à partir de laquelle vous pouvez obtenir les noms des paramètres.
  3. La dernière étape consiste à joindre les 2 tableaux ensemble en 1 tableau qui a 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
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

Wow déjà tant de réponses .. Im assez sûr que cela soit enterré. Même si je pensais que cela pourrait être utile pour certains.

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

Cette fonction est très utile à des fins de débogage, par exemple: la journalisation de la fonction appelée avec ses paramètres, les valeurs des paramètres par défaut et les arguments.

J'ai passé un peu de temps là-dessus hier, à trouver le bon RegExp pour résoudre ce problème et c'est ce que j'ai trouvé. Cela fonctionne très bien et je suis très satisfait 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 transpilateur Babel les supprime de la fonction. Si vous l'exécutiez dans le dernier NodeJS, cela fonctionne 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 de flèche en ligne comme valeur par défaut. Cela rend tout simplement compliqué l'extraction des valeurs à l'aide d'un RegExp.

Veuillez me faire savoir si cela vous a été utile! J'adorerais entendre des commentaires!


1

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();

Cet exemple est juste pour obtenir le nom des paramètres pour vous.
myzhou

Cette réponse est utile, mais elle ne répond pas à la question. J'ai voté parce que cela a résolu mon problème, mais je pense qu'il devrait être déplacé vers un endroit plus approprié. Peut-être rechercher des questions connexes?
chharvey

1

Ce package utilise une refonte afin de créer un AST, puis les noms des paramètres sont collectés à partir de leur, ce qui lui permet de prendre en charge la correspondance de modèles, les arguments par défaut, les fonctions fléchées et d'autres fonctionnalités ES6.

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


1

J'ai modifié la version tirée d' AngularJS qui implémente un mécanisme d'injection de dépendances pour fonctionner sans Angular. J'ai également mis à jour l' STRIP_COMMENTSexpression régulière pour qu'elle fonctionne ECMA6, elle prend donc en charge des éléments tels que 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({})


0

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

Les arguments ne sont-ils pas obsolètes? Voir la suggestion d'Ionut G. Stan ci-dessus.
vikasde

vikasde a raison. L'accès à la argumentspropriété d'une instance de fonction est obsolète. Voir developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan

0

C'est assez simple.

Au début, il y a une obsolète arguments.callee- 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, donc 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 noms de variables exacts, utilisez simplement une variable interne de fonctionarguments sans aucune magie.

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

Tous vont appeler toString et 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: Jack Allan a été fourni une solution un peu meilleure en fait. GJ Jack!


Cela pourrait être encore plus simple si vous l'avez utilisé à la SomeFuncNameplace de arguments.callee(les deux pointent vers l'objet fonction lui-même).
Raphael Schweikert

0

Quelle que soit la solution, elle ne doit pas casser les fonctions bizarres, dont l' toString()apparence est tout aussi bizarre:

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

capture d'écran de la console

Aussi, pourquoi utiliser des expressions régulières complexes? Cela peut se faire comme:

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

Cela fonctionne partout avec chaque fonction, et le seul regex est la suppression des espaces blancs qui ne traite même pas la chaîne entière en raison de l' .splitastuce.


0

Ok donc une vieille question avec plein de réponses adéquates. voici mon offre qui n'utilise pas l'expression régulière, à l'exception de la tâche subalterne de dépouiller les espaces. (Je dois noter que la fonction "strips_comments" les espace en fait, plutôt que de les supprimer physiquement. C'est parce que je l'utilise ailleurs et pour diverses raisons, j'ai besoin que les emplacements des jetons non-commentaires d'origine restent intacts)

C'est un bloc de code assez long car ce collage comprend 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

Voici une façon:

// 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 qui prennent en charge arguments.callee.


2
La boucle while entraîne une boucle infinie avec le code fourni (à partir de 20171121at1047EDT)
George 2.0 Hope

@ George2.0Hope Merci de l'avoir signalé. Je mettrai à jour la réponse.
Ates Goral

args.toSource n'est pas une fonction (ligne 20) ... mais même si vous la changez en: console.log (args.toString ()); ... vous obtenez ... [objet Object] ... mieux si vous le faites: console.log (JSON.stringify (args));
George 2.0 Hope

Les choses ont bien changé depuis 2009!
Ates Goral

1
au cas où quelqu'un se présenterait ici pour React, cette fonction, qui est géniale, ne fonctionnera pas en mode strict.
Individuel11

0

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)

-1

fonction paramètre chaîne valeur image dynamiquement à partir de JSON . Étant donné que item.product_image2 est une chaîne d'URL, vous devez la mettre entre guillemets lorsque vous appelez changeImage à l'intérieur du paramètre.

Ma fonction Onclick

items+='<img src='+item.product_image1+' id="saleDetailDivGetImg">';
items+="<img src="+item.product_image2+"  onclick='changeImage(\""+item.product_image2+"\");'>";

Ma fonction

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.