How to get function parameter names/values dynamically?

Viewed 246393

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?

34 Answers

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

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

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

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.

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

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://stackoverflow.com/a/53041387/1087372

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

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

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

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.

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

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 !== "");
}

Try Manually:

function something(arg1, arg2) {
  console.log ( arg1 + arg2 );
}
Related