How to get function parameter names/values dynamically?
Asked Answered
M

35

351

Is there a way to get the function parameter names of a function dynamically?

Let’s say my function looks like this:

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

Now, how would I get a list of the parameter names and their values into an array from inside the function?

Mixtec answered 17/6, 2009 at 15:57 Comment(1)
Thanks to everybody. After searching around, I found the solution on SO: #915468 It uses a regex to get the param name. Its probably not the best solution, however it works for me.Mixtec
P
367

The following function will return an array of the parameter names of any function passed in.

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

Example usage:

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 []

Edit:

With the invent of ES6 this function can be tripped up by default parameters. Here is a quick hack which should work in most cases:

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

I say most cases because there are some things that will trip it up

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

Edit: I also note vikasde wants the parameter values in an array also. This is already provided in a local variable named arguments.

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

The arguments object is not an Array. It is similar to an Array, but does not have any Array properties except length. For example, it does not have the pop method. However it can be converted to a real Array:

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

If Array generics are available, one can use the following instead:

var args = Array.slice(arguments);
Platt answered 29/3, 2012 at 11:30 Comment(27)
Note that this solution may fail because of comments and spaces - for example: var fn = function(a /* fooled you)*/,b){}; will result in ["a", "/*", "fooled", "you"]Cronyism
This and @Lambder's angular solution get the parameter names, but how do you get the values?Farrica
@Cronyism You could use this in conjunction with the AngularJS code to strip comments shown in Lambder's answer in order to avoid comments breaking this.Boito
@Boito Yes! I've also added that comment to the bottom of this page long time ago.Cronyism
Updated my example to include regex to strip commentsPlatt
I modified the function to return an empty array (instead of null) when there aren't any argumentsBypass
I'm curious, why is STRIP_COMMENTS outside of the scope of the function? I understand that it works, but is there any reason to do it that way? Great tool!Unlikely
There is a cost to compiling a regex, therefore you want to avoid compiling complex regexs more than once. This is why it is done outside the functionPlatt
The comment stripping regex can be siimplified, by observing that: '[\s\S]' can be replaced with '.' This is because a character class that matches any character in a set or it's complement will match any character. All of these regex's match any single character: '[\s\S]', [\w\W]', '[\d\D]' or '.' So, /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg can be replaced with /((\/\/.*$)|(\/*.*?*\/))/mg; jsfiddle.net/tgoneil/6K57RIcj
CORRECTION: Was going to modify the regex with a /s modifier that perl allows so '.' can also match newline. This is necessary for multi-line comments inside /* */. Turns out Javascript regex doesn't allow the /s modifier. The original regex using [/s/S] does match newline characters. SOOO, please disregard the previous comment.Icj
I did a jsperf between this answer and a mod of the Angular JS answer - @jack-allen answer wins over Angular: jsperf.com/get-function-parameter-names.Caroncarotene
@Caroncarotene Note you are including the regex compilation in your tests. These should only be done once. Your results may be different if you move the regex compilation to the setup step instead of the testPlatt
Updated with a hack to make the code work with ES6 default parametersPlatt
Can we know the name of actual arguments passed in the function? like func(a,b,c,d); when we call this I want a,b,c,d printed in outputJeer
@Shishir that is what this function returnsPlatt
#31318024Jeer
With ES6 arrow functions having single argument like (a => a*10) it fails to give desired output.Anthroposophy
the es6 stuff is all nice but in a prod env we're still having transpiled code to es5 which means the default params are within the function declared on top (see babel)Shindig
@Jack Allan I truly have no idea - the answer is not to the question actually. The question is asking about how to get the parameter names inside of the function when the function invoked instead of defined. I currently just met the same issue (trying to detect the method by the parameter name and then pass the parameter value to the name-oriented method). But weirdly enough is that it's up voted by most people. Perhaps it's well-coded but actually not helping the question. Thank you.Gabby
In chrome, the above getParamNames(...) function just returns an array with the string 'e' inside. Just tried it and got 0: "e" in the dev console. Any idea why?Police
I would not use this in production; default parameters have too many problems with this kind of approach. For example, getParamNames(function (a = function (b = 4, c = 5) {}){}) reports ["a", "c"]. A safer way would be to leverage an abstract syntax tree rather than regular expressions.Denunciation
let var = [...arguments]... for param values, updated to reflect ES6 and later. Just sayin'.Armin
A few people have said this doesn't actually answer the question. And that the actual question is how can I know the names of the variables that were used in the function call that invoked my function? I did not assume that to be the question because it is a really bad idea for any language to allow such a thing to be possible. For a start the function call might be made with literals instead of variables. Also any language that allows this would not allow the programmer to keep the implementation details of a function hidden. This makes any kind of real programming totally impractical. So toPlatt
(Continued)So to anyone finding my answer expecting it to answer this question and not what I assumed the question was I offer this advice: what you are trying to do is most certainly a bad idea and there is no doubt in my mind that, even if it were possible -which thankfully it is not- there are zero good reasons to do this. Please re evaluate your problem because you're not solving it correctly.Platt
here's a good reason: having a superclass with async functions verify that a subclass in fact implements those same functions as async instead of bare, so that it can throw an error on instantiation, rather than much later when code tries to invoke the offending function. You want the error to say which function that is, and sure, you could omit the arguments, but it makes for an easier code dive if you see the full function signature instead. Is this niche? incredibly, yeah. Is it a valid use-case? Also yes ;)Cuprite
Doesn't work for lambdas without parens: async e => {...}. Try my solution here: https://mcmap.net/q/92893/-how-to-get-function-parameter-names-values-dynamicallyHemicrania
Use Array.from.Ontology
V
131

Below is the code taken from AngularJS which uses the technique for its dependency injection mechanism.

And here is an explanation of it taken from http://docs.angularjs.org/tutorial/step_05

Angular's dependency injector provides services to your controller when the controller is being constructed. The dependency injector also takes care of creating any transitive dependencies the service may have (services often depend upon other services).

Note that the names of arguments are significant, because the injector uses these to look up the dependencies.

/**
 * @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;
}
Vitriolic answered 24/8, 2012 at 11:39 Comment(7)
@apaidnerd with the blood of demons and spawn of satan, apparently. Regex?! Would be cool if there was a built in way in JS, wouldn't it.Sensory
@apaidnerd, so true! Just thought- how in the hell is that implemented? Actually I thought about using functionName.toString() but I hoped for something more elegant (and perhaps faster)Milepost
@sasha.sochka, came here wondering the exact same thing, after realizing there was no built in way to get parameter names with javascriptFraternity
to save people time , you can get this function from angular via annotate = angular.injector.$$annotateJernigan
I did a jsperf between a mod of this answer and the chosen jack-allan answer - jack-allen answer wins over Angular: jsperf.com/get-function-parameter-namesCaroncarotene
If you're already using Angular, you can use its $injector service: $injector.invoke(function(serviceA){});Hargett
I literally went searching on the Internet for this topic because I was curious how Angular did it... now I know and also I know too much!Edmea
G
64

Here is an updated solution that attempts to address all the edge cases mentioned above in a compact way:

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 [""]
}  

Abbreviated test output (full test cases are attached below):

'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>
Granary answered 2/7, 2015 at 21:28 Comment(9)
This breaks when one-line comments are present. Try this: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespaceEtheridge
Good catch -- I've updated the example with your suggested fix and also added a corresponding test case to the code snippet. Thanks!Granary
You should probably replace func + '' with Function.toString.call(func) to defend against the case when the function has a custom .toString() implementation.Balanchine
Was wondering how to defend against that and would like to incorporate your suggestion. Non-function values would still need to be coerced -- do you think it's worth aiming for compactness: (/^f/.test(typeof func) ? $args.toString.call(func) : func+''), or in this case better to just spell it out: (typeof func === 'function' ? Function.toString.call(func) : func+'')?Granary
fat arrows => .split(/\)[\{=]/, 1)[0]Merrow
this splits destructured objects (like ({ a, b, c })) into all the parameters inside the destructuring. to keep destructured objects intact, change the last .split to: .split(/,(?![^{]*})/g)Cristinecristiona
This will also not work when there are default string values that contain "//" or "/*"Cremate
I have a function with parameter destructing like function ({test = '22'} = {}) {return test;}. $args() gives me an output of ['{test']. could you fix that. Thank you :)Harar
What about lambdas, with or without parens?Hemicrania
C
25

Solution that is less error prone to spaces and comments would be:

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

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

["hi", "you"]
Cronyism answered 2/2, 2013 at 8:26 Comment(1)
@AlexMills One thing I've noticed is that the Spec for Arrow Functions says that they shouldnt be treated as 'functions.' Meaning it wouldn't be appropriate for this to match the array functions. The 'this' is not set the same way, and they shouldnt be invoked as functions either. It was something I learned the hard way. ($myService) => $myService.doSomething() looks cool, but it's a misuse of the Array Functions.Sunset
T
22

A lot of the answers on here use regexes, this is fine but it doesn't handle new additions to the language too well (like arrow functions and classes). Also of note is that if you use any of these functions on minified code it's going to go 🔥. It will use whatever the minified name is. Angular gets around this by allowing you to pass in an ordered array of strings that matches the order of the arguments when registering them with the DI container. So on with the 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');
};

This handles the original parse issue and a few more function types (e.g. arrow functions). Here's an idea of what it can and can't handle as is:

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

Depending on what you want to use it for ES6 Proxies and destructuring may be your best bet. For example if you wanted to use it for dependency injection (using the names of the params) then you can do it as follows:

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

It's not the most advanced resolver out there but it gives an idea of how you can use a Proxy to handle it if you want to use args parser for simple DI. There is however one slight caveat in this approach. We need to use destructuring assignments instead of normal params. When we pass in the injector proxy the destructuring is the same as calling the getter on the object.

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

This outputs the following:

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

Its wired up the entire application. The best bit is that the app is easy to test (you can just instantiate each class and pass in mocks/stubs/etc). Also if you need to swap out implementations, you can do that from a single place. All this is possible because of JS Proxy objects.

Note: There is a lot of work that would need to be done to this before it would be ready for production use but it does give an idea of what it would look like.

It's a bit late in the answer but it may help others who are thinking of the same thing. 👍

Tefillin answered 7/1, 2017 at 18:57 Comment(0)
P
21

I know this is an old question, but beginners have been copypasting solutions that extract parameter names from the string representation of a function as if this was good practice in any code. Most of the time, this just hides a flaw in the logic.

Writing parameter names in the parentheses of a function declaration can be seen as a shorthand syntax for variable creation. This:

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

...is akin to this:

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

    console.log("does something");
}

The variables themselves are stored in the function's scope, not as properties in an object. Just like you can't manipulate the name of a variable with code, there is no way to retrieve the name of a parameter as it is not a string, and it could be eliminated during JIT compilation.

I always thought of the string representation of a function as a tool for debugging purposes, especially because of this arguments array-like object. You are not required to give names to the arguments in the first place. If you try parsing a stringified function, it doesn't actually tell you about extra unnamed parameters it might take.

Here's an even worse and more common situation. If a function has more than 3 or 4 arguments, it might be logical to pass it an object instead, which is easier to work with.

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

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

In this case, the function itself will be able to read through the object it receives and look for its properties and get both their names and values, but trying to parse the string representation of the function would only give you "obj" for parameters, which isn't useful at all.

Pigpen answered 20/8, 2015 at 2:19 Comment(5)
I think the case for something like this is usually: debugging/logging, some sort of decorator that does funky stuff (technical term 😁), or building a dependency injection framework for your apps to inject automatically based on argument name (this is how angular works). Another really interesting use case is in promisify-node (it's a library that takes a function that normally takes a callback and then converts it to a Promise). They use this to find common names for callbacks (like cb/callback/etc) and then they can check if the function is async or sync before wrapping it.Tefillin
See this file for their parser. It's a bit naive but it handles the majority of cases.Tefillin
Interesting, I'm surprised such a library got any attention. Well, there are multiple open issues about problematic situations like the ones I described. As I said, if it's for debugging purposes it's fine, but relying on string conversion of functions in production environments is way too risky.Pigpen
Agreed, it's a bit messy to handle it like this. The best and most straightforward use is dependency injection. In that case you own the code and can handle naming and other areas of code. I think this is where it would see the most use. Im currently using esprima (instead of regex) and ES6 Proxy (constructor trap and apply trap) and Reflection to handle DI for some of my modules. It's reasonably solid.Tefillin
There is a little bit of me that would love to include this in an angular app and see how long it takes our developers to debug it. 😈😁 Might be a good one for a long interview question! 🤔Tefillin
E
19

I have read most of the answers here, and I would like to add my one-liner.

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

or

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

or for a one-liner function in ECMA6

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

__

Let's say you have a function

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

The below code will return "abc,def,ghi,jkl"

That code will also work with the setup of a function that Camilo Martin gave:

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

Also with Bubersson's comment on Jack Allan's answer:

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

__

Explanation

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

This creates a Regular Expression with the new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). I have to use new RegExp because I am injecting a variable (Function.name, the name of the function being targeted) into the RegExp.

Example If the function name is "foo" (function foo()), the RegExp will be /foo\s*\((.*?)\)/.

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

Then it converts the entire function into a string, and removes all newlines. Removing newlines helps with the function setup Camilo Martin gave.

.exec(...)[1]

This is the RegExp.prototype.exec function. It basically matches the Regular Exponent (new RegExp()) into the String (Function.toString()). Then the [1] will return the first Capture Group found in the Regular Exponent ((.*?)).

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

This will remove every comment inside /* and */, and remove all spaces.


This also now supports reading and understanding arrow (=>) functions, such as f = (a, b) => void 0;, in which Function.toString() would return (a, b) => void 0 instead of the normal function's function f(a, b) { return void 0; }. The original regular expression would have thrown an error in its confusion, but is now accounted for.

The change was from new RegExp(Function.name+'\\s*\\((.*?)\\)') (/Function\s*\((.*?)\)/) to new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') (/(?:Function\s*|^)\((.*?)\)/)


If you want to make all the parameters into an Array instead of a String separated by commas, at the end just add .split(',').

Evictee answered 31/8, 2016 at 15:42 Comment(5)
quite nice. one line, handles arrow functions, no external dependencies, and covers more than enough edge cases, at least for my intended use. if you can make some reasonable assumptions about the function signatures you'll be dealing with this is all you'll need. thanks!Lorenalorene
Doesn't work with this simple arrow function: f = (a, b) => void 0; on getParameters(f) I get TypeError: Cannot read property '1' of nullSappanwood
@AT I have just updated the answer to fix support for your issueEvictee
Thanks… but keep in mind that parentheses are no longer required, so you can do things like getParameters(a => b => c => d => a*b*c*d), which with your code still gives that TypeError: Cannot read property '1' of null… whereas this one works stackoverflow.com/a/29123804Sappanwood
Does not work when function has default values (role, name="bob") The extracted parameter is name="bob" instead of expected "name"Siena
C
14

Since JavaScript is a scripting language, I feel that its introspection should support getting function parameter names. Punting on that functionality is a violation of first principles, so I decided to explore the issue further.

That led me to this question but no built-in solutions. Which led me to this answer which explains that arguments is only deprecated outside the function, so we can no longer use myFunction.arguments or we get:

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

Time to roll up our sleeves and get to work:

⭐ Retrieving function parameters requires a parser because complex expressions like 4*(5/3) can be used as default values. So Gaafar's answer or James Drew's answer are so far the best approaches.

I tried the babylon and esprima parsers but unfortunately they can't parse standalone anonymous functions, as pointed out in Mateusz Charytoniuk's answer. I figured out another workaround though by surrounding the code in parentheses, so as not to change the logic:

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

The newlines prevent issues with // (single-line comments).

⭐ If a parser is not available, the next-best option is to use a tried-and-true technique like Angular.js's dependency injector regular expressions. I combined a functional version of Lambder's answer with humbletim's answer and added an optional ARROW boolean for controlling whether ES6 fat arrow functions are allowed by the regular expressions.


Here are two solutions I put together. Note that these have no logic to detect whether a function has valid syntax, they only extract the arguments. This is generally ok since we usually pass parsed functions to getArguments() so their syntax is already valid.

I will try to curate these solutions as best I can, but without effort from the JavaScript maintainers, this will remain an open problem.

Node.js version (not runnable until StackOverflow supports 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() {}');

Full working example:

https://repl.it/repls/SandybrownPhonyAngles

Browser version (note that it stops at the first complex default value):

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() {}');

Full working example:

https://repl.it/repls/StupendousShowyOffices

Centrepiece answered 28/3, 2018 at 21:18 Comment(4)
if I'm not mistaken, in your browser version, the first conditional in FUNC_ARGS will work for both arrow and traditional functions, so you don't need the second part and so you can do away with the dependency on ARROW.Hereunder
This is great! I was looking for a solution like this that uses a parser to cover ES6 syntax. I’m planning on using this to create a jest “implements interface” matcher because simply using function.length has limitations with default parameters, and I wanted to be able to assert rest parameters.Arsenault
It is worth pointing out that the fifth test case that contains parentheses in the default value currently fails. I wish my regex-fu was strong enough to fix, sorry!Unruh
@DaleAnderson hey you're right, and it threw me for a moment until I re-read my answer. It's failing for the browser version because it uses a regex, while the Node.js example uses the babylon parser. So you correctly identified the limitations of regex, the main one being (if I recall) that a regex can't process recursive/nested structures. If you search for (note that it stops at the first complex default value) that's what I meant by that. Sorry for the inconvenience!Centrepiece
Z
10

You can also use "esprima" parser to avoid many issues with comments, whitespace and other things inside parameters list.

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

It works even with code like this:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]
Zwinglian answered 26/2, 2014 at 13:39 Comment(0)
M
10

The proper way to do this is to use a JS parser. Here is an example using 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' ]

The code here finds the names of the three (formal) parameters of the function f. It does so by feeding f into acorn.parse().

Moat answered 1/7, 2018 at 22:33 Comment(1)
what about the values ?Ciliate
T
9
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

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

Ternary answered 2/8, 2013 at 7:19 Comment(1)
This dosen't work for many cases, including any code with newline char (\r\n), and any func code that includes ( and ) chars inside its function body! eg: myFunc(p1, p2) { if(p1>0){} }Bouffard
N
8

I've tried doing this before, but never found a praticial way to get it done. I ended up passing in an object instead and then looping through it.

//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
});
Nympha answered 17/6, 2009 at 16:0 Comment(2)
I have to many functions already pre-defined that are being called with standard parameters instead of a single object. Changing everything would take to much time.Mixtec
This response is not the answer to the original question which was defined precisely. It shows a solution for completely different problem. The original question refers to the technique AngularJS uses fror its dependency injection. Argument names are meaningful as they correspond to dependencies of the module which DI automatically provides.Vitriolic
P
5

I don't know if this solution suits your problem, but it lets you redefine whatever function you want, without having to change code that uses it. Existing calls will use positioned params, while the function implementation may use "named params" (a single hash param).

I thought that you will anyway modify existing function definitions so, why not having a factory function that makes just what you want:

<!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>

Hope it helps.

Pseudo answered 17/6, 2009 at 16:50 Comment(0)
L
4

Taking the answer from @jack-allan I modified the function slightly to allow ES6 default properties such as:

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

to still return [ '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;
    }
}
Labradorite answered 18/3, 2015 at 13:46 Comment(1)
Thanks, yours is the only one in this thread which worked for me.Sappanwood
D
4

As this has not yet been mentioned, if you are using Typescript you can emit meta-data when using Decorators which will allow you to get the parameter types.

Metadata will only be emitted if the class/function/prop has a decorator on it.
It doesn't matter which decorator.

This feature can be enabled by setting emitDecoratorMetadata to true inside tsconfig.json

{
  "compilerOptions": {
    "emitDecoratorMetadata": true
  }
}

As the metadata is still an early proposal the reflect-metadata package must be installed or Reflect.getMetadata will not be defined.

npm install reflect-metadata

You can use it as follows:

const AnyDecorator = () : MethodDecorator => {
    return target => { }
}

class Person{
    @AnyDecorator()
    sayHello(other: Person){}
}
const instance = new Person();
// This returns: Function
const funcType = Reflect.getMetadata('design:type', instance.sayHello);
// Returns an array of types, here it would be: [Person]
const funcParams = Reflect.getMetadata('design:paramtypes', instance.sayHello);

In newer versions of Angular for instance this is used to determine what to inject -> https://mcmap.net/q/94071/-emitdecoratormetadata-and-its-importance-in-transpiled-code

Drue answered 2/10, 2020 at 9:41 Comment(3)
Does it put a list of a function's parameter names inside the function?Krone
@Krone getMetadata('design:paramtypes', x) will only return the types, as an example [String]Drue
I learned from your answer so I'm upvoting - it helped me understand that reflect-metadata won't help because I need the params namesShoebill
G
3

I don't know how to get a list of the parameters but you can do this to get how many it expects. Note this only counts arguments without a default value in the signature:

function foobar(a, b, c) {}
function foobar2(a, b=false, c=false) {}

console.log(foobar.length); // prints 3
console.log(foobar2.length); // prints 1
Gasman answered 17/6, 2009 at 16:0 Comment(1)
function gotcha (a, b = false, c) {}; alert(gotcha.length)Annul
H
2
//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
Hyperdulia answered 28/1, 2013 at 3:18 Comment(0)
C
2
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;
}
Coracoid answered 21/11, 2013 at 18:32 Comment(0)
K
2

The answer to this requires 3 steps:

  1. To get the values of the actual parameters passed to the function (let's call it argValues). This is straight forward as it will be available as arguments inside the function.
  2. To get the parameter names from the function signature (let's call it argNames). This not as easy and requires parsing the function. Instead of doing the complex regex yourself and worrying about edge cases (default parameters, comments, ...), you can use a library like babylon that will parse the function into an abstract syntax tree from which you can obtain the names of parameters.
  3. The last step is to join the 2 arrays together into 1 array that has the name and value of all the parameters.

The code will be like this

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)

and the logged object will be

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

And here's a working example https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

Kinglet answered 17/6, 2016 at 12:42 Comment(0)
H
2

Wow so many answers already.. Im pretty sure this gets buried. Even so I figured this might be useful for some.

I wasn't fully satisfied with the chosen answers as in ES6 it doesn't work well with default values. And it also does not provide the default value information. I also wanted a lightweight function that does not depend on an external lib.

This function is very useful for debugging purposes, for example: logging called function with its params, default param values and arguments.

I spent some time on this yesterday, cracking the right RegExp to solve this issue and this is what I came up with. It works very well and I'm very pleased with the outcome:

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\'' ] ]

As you can tell some of the parameter names disappear because the Babel transpiler removes them from the function. If you would run this in the latest NodeJS it works as expected (The commented results are from NodeJS).

Another note, as stated in the comment is that is does not work with inlined arrow functions as a default value. This simply makes it far to complex to extract the values using a RegExp.

Please let me know if this was useful for you! Would love to hear some feedback!

Haugh answered 25/12, 2016 at 16:16 Comment(0)
A
2

This package uses recast in order to create an AST and then the parameter names are gathered from their, this allows it to support pattern matching, default arguments, arrow functions and other ES6 features.

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

Armilla answered 14/10, 2017 at 17:31 Comment(0)
H
2

Here's my solution -- it works for named and unnamed functions, async and non-async functions, async and non-async lambdas, and lambdas with and without parens.

const STRIP_COMMENTS = /((\/\/.*$)|(\/\*.*\*\/))/mg;
const STRIP_KEYWORDS = /(\s*async\s*|\s*function\s*)+/;
const ARGUMENT_NAMES = /\(([^)]+)\)\s*=>|([a-zA-Z_$]+)\s*=>|[a-zA-Z_$]+\(([^)]+)\)|\(([^)]+)\)/;
const ARGUMENT_SPLIT = /[ ,\n\r\t]+/;
function getParamNames(func) {
    const fnStr = func.toString()
        .replace(STRIP_COMMENTS, "")
        .replace(STRIP_KEYWORDS, "")
        .trim();
    const matches = ARGUMENT_NAMES.exec(fnStr);
    var match;
    if (matches) {
        for (var i = 1; i < matches.length; i++) {
            if (matches[i]) {
                match = matches[i];
                break;
            } 
        }
    }
    if (match === undefined) {
        return [];
    }
    return match.split(ARGUMENT_SPLIT).filter(part => part !== "");
}
Hemicrania answered 15/9, 2022 at 20:50 Comment(0)
B
1

How I typically do it:

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

You can even ref the args by the functions name like:

name.arguments;

Hope this helps!

Barilla answered 20/8, 2012 at 1:17 Comment(2)
And where are the names of function parameters?Emmett
Ah... You mean you want it in hash-form? As if: var args = name.arguments; console.log('I WANNa SEE', args); output something like "{arg1: {...}, arg2: 'string'}"? This might clear things up: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Function Arguments are an 'Array'-like object, having Enumerable indices. I don't think a hash-mapping is possible, but you could just pass an Object-literal in which is good practice if you have more than 4 params anyway.Barilla
B
1

I'll give you a short example below:

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();
Brokenhearted answered 19/5, 2017 at 7:29 Comment(2)
That example is just to get the parameters' name for you.Brokenhearted
This answer is useful, but it does not answer the question. I voted up because it solved my problem, but I think it should be moved somewhere more appropriate. Perhaps search for related questions?Jaquelynjaquenetta
G
1

I have modified the version taken from AngularJS that implements a dependency injection mechanism to work without Angular. I have also updated the STRIP_COMMENTS regex to work with ECMA6, so it supports things like default values in the signature.

var FN_ARGS = /^\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({})
Gantrisin answered 29/11, 2017 at 23:28 Comment(0)
T
1

Here is another way to parse out the input parameters.

const func = (p1,p2=3,...args) => args.reduce((a,b)=>a+b-p1-p2)
const regex = /\((.*?)\)/
regex.exec(func.toString())[1].split(",")

It works by converting the function into a string and then getting only the values between the brackets.

Torrefy answered 7/12, 2023 at 17:13 Comment(0)
C
0

You can access the argument values passed to a function using the "arguments" property.

    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]);    
Colossians answered 17/6, 2009 at 18:11 Comment(2)
Isn't arguments deprecated? See the suggestion of Ionut G. Stan above.Mixtec
vikasde is right. Accessing the arguments property of a function instance is deprecated. See developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…Popliteal
P
0

Here's one way:

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

Note: This only works on browsers that support arguments.callee.

Pitanga answered 17/6, 2009 at 19:14 Comment(5)
The while loop results in an infinite loop with the code provided (as of 20171121at1047EDT)Skin
@George2.0Hope Thanks for pointing that out. I'll update the answer.Pitanga
args.toSource is not a function (line 20) ... but even if you change it to: console.log(args.toString()); ... you get ... [object Object] ... better if you do : console.log(JSON.stringify(args));Skin
Things have changed quite a bit since 2009!Pitanga
in case anyone shows up here for React, this function, which is awesome, will not work in strict mode.Talbott
K
0

It's pretty easy.

At the first there is a deprecated arguments.callee — a reference to called function. At the second if you have a reference to your function you can easily get their textual representation. At the third if you calling your function as constructor you can also have a link via yourObject.constructor. NB: The first solution deprecated so if you can't to not use it you must also think about your app architecture. If you don't need exact variable names just use inside a function internal variable arguments without any magic.

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

All of them going to call toString and replace with re so we can create a helper:

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

Some examples:

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

Enjoy with JS!

UPD: Jack Allan was provided a little bit better solution actually. GJ Jack!

Katusha answered 1/2, 2013 at 23:30 Comment(1)
This could be even more straightforward if you used SomeFuncName instead of arguments.callee (both point to the function object itself).Georgettageorgette
K
0

Whatever the solution, it must not break on wierd functions, whose toString() looks just as wierd:

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

screenshot from console

Also, why use complex regular expressions? This can be done like:

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

This works everywhere with every function, and the only regex is whitespace removal that doesn't even process the whole string due to the .split trick.

Krantz answered 19/5, 2015 at 10:2 Comment(1)
function sum(a , b = (5/7) * 3, c= String({d:1,b:2,cc:3})["cc"], n ) { return e+t} breaks itGaud
D
0

Ok so an old question with plenty of adequate answers. here is my offering that does not use regex, except for the menial task of stripping whitespace . (I should note that the "strips_comments" function actually spaces them out, rather than physically remove them. that's because i use it elsewhere and for various reasons need the locations of the original non comment tokens to stay intact)

It's a fairly lengthy block of code as this pasting includes a mini test framework.

    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);
Dosage answered 3/8, 2016 at 10:35 Comment(0)
R
0

Note: if you want to use ES6 parameter destructuring with the top solution add the following line.

if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)
Roley answered 5/1, 2018 at 22:47 Comment(0)
Y
0

i would likke to suggest solution which supports arrow functions like i used this article for basic regular expression and https://davidwalsh.name/javascript-arguments and added arrow functions support

(arg1,arg2) => {}

or

arg => {}



function getArgs(func) {
  if(func.length === 0){
      return []
  }

  let string = func.toString();

  let args;
  // First match everything inside the function argument parens. like `function (arg1,arg2) {}` or `async function(arg1,arg2) {}


  args = string.match(/(?:async|function)\s*.*?\(([^)]*)\)/)?.[1] ||
      // arrow functions with multiple arguments  like `(arg1,arg2) => {}`
         string.match(/^\s*\(([^)]*)\)\s*=>/)?.[1] ||
      // arrow functions with single argument without parens like `arg => {}`
         string.match(/^\s*([^=]*)=>/)?.[1]

  // Split the arguments string into an array comma delimited.
  return args.split(',').map(function(arg) {
    // Ensure no inline comments are parsed and trim the whitespace.
    return arg.replace(/\/\*.*\*\//, '').trim();
  }).filter(function(arg) {
    // Ensure no undefined values are added.
    return arg;
  });
}
Yak answered 23/10, 2020 at 18:37 Comment(2)
seems to fail for inline comments containing a close-paren, e.g. function foo(x, y /* FIXME: (temp), z */, w) {Garneau
I tried to edit but "suggested edit queue is full". You can fix the issue reported by Dan O by moving the "replace(/\/*.**\//, '')" in the return statement near the bottom to the end of the "string = func.toString()" declaration near the top. Make sure to keep the trim() in the return statement.Ferric
P
0

This one is pretty good for me:

function parseArgs(fn, options = {
    ignoreWithDefault: true,
    ignoreWithComment: true
}) {

    const regex = /\((.|\s)*\)/gm;

    const m = regex.exec(fn.toString())

    let normalised = m[0]
    if (options.ignoreWithComment) {
        normalised = normalised.replaceAll(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "")
    }
    return normalised.replaceAll(/[ ()]/gm, "")
        .split(/[\r|\n|,]/gm)
        .filter((item) => {
            if(item === "") return false
            if(options.ignoreWithDefault){
                return !item.includes("=");
            }
            return true
        })
}

const a = (a = (4/5), b, c, D4, /*fasdf */g5,
    //
    //g
    f_123,
    asdf
) => {
}

console.log(parseArgs(a))
Pumpkin answered 5/9, 2023 at 21:34 Comment(0)
B
-1

function parameter string value image dynamically from JSON. Since item.product_image2 is a URL string, you need to put it in quotes when you call changeImage inside parameter.

My Function Onclick

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

My Function

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
Barron answered 29/7, 2015 at 6:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.