How to get function parameter names/values dynamically?

229,244

Solution 1

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

Solution 2

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

Solution 3

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>

Solution 4

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

Solution 5

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. 👍

Share:
229,244
vikasde
Author by

vikasde

Updated on June 23, 2021

Comments

  • vikasde
    vikasde about 3 years

    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?

  • vikasde
    vikasde about 15 years
    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.
  • vikasde
    vikasde about 15 years
    Isn't arguments deprecated? See the suggestion of Ionut G. Stan above.
  • user5880801
    user5880801 about 15 years
    vikasde is right. Accessing the arguments property of a function instance is deprecated. See developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
  • Lambder
    Lambder almost 12 years
    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.
  • Andrej
    Andrej over 11 years
    And where are the names of function parameters?
  • bubersson
    bubersson over 11 years
    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"]
  • CWSpear
    CWSpear about 11 years
    This and @Lambder's angular solution get the parameter names, but how do you get the values?
  • Raphael Schweikert
    Raphael Schweikert about 11 years
    This could be even more straightforward if you used SomeFuncName instead of arguments.callee (both point to the function object itself).
  • JBarnes
    JBarnes almost 11 years
    @bubersson 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.
  • bubersson
    bubersson almost 11 years
    @JBarnes Yes! I've also added that comment to the bottom of this page long time ago.
  • Jack Allan
    Jack Allan almost 11 years
    Updated my example to include regex to strip comments
  • Aditya M P
    Aditya M P almost 11 years
    @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.
  • B T
    B T almost 11 years
    I modified the function to return an empty array (instead of null) when there aren't any arguments
  • sasha.sochka
    sasha.sochka almost 11 years
    @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)
  • Cody
    Cody over 10 years
    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.
  • christianbundy
    christianbundy about 10 years
    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!
  • Jack Allan
    Jack Allan about 10 years
    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 function
  • tgoneil
    tgoneil about 10 years
    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/6K57R
  • tgoneil
    tgoneil about 10 years
    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.
  • Hart Simha
    Hart Simha over 9 years
    @sasha.sochka, came here wondering the exact same thing, after realizing there was no built in way to get parameter names with javascript
  • Nick
    Nick over 9 years
    to save people time , you can get this function from angular via annotate = angular.injector.$$annotate
  • andes
    andes about 9 years
    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.
  • andes
    andes about 9 years
    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-names
  • Jack Allan
    Jack Allan about 9 years
    @andes 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 test
  • Jack Allan
    Jack Allan about 9 years
    Updated with a hack to make the code work with ES6 default parameters
  • Shishir Arora
    Shishir Arora almost 9 years
    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 output
  • Jack Allan
    Jack Allan almost 9 years
    @Shishir that is what this function returns
  • Shishir Arora
    Shishir Arora almost 9 years
  • Dennis Hackethal
    Dennis Hackethal over 8 years
    If you're already using Angular, you can use its $injector service: $injector.invoke(function(serviceA){});
  • Merlyn Morgan-Graham
    Merlyn Morgan-Graham over 8 years
    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 whitespace
  • humbletim
    humbletim about 8 years
    Good catch -- I've updated the example with your suggested fix and also added a corresponding test case to the code snippet. Thanks!
  • Steven Hunt
    Steven Hunt almost 8 years
    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!
  • Andrew T Finnell
    Andrew T Finnell almost 8 years
    @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.
  • Paul Go
    Paul Go over 7 years
    You should probably replace func + '' with Function.toString.call(func) to defend against the case when the function has a custom .toString() implementation.
  • humbletim
    humbletim over 7 years
    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+'')?
  • Matt
    Matt over 7 years
    fat arrows => .split(/\)[\{=]/, 1)[0]
  • James Drew
    James Drew over 7 years
    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.
  • James Drew
    James Drew over 7 years
    See this file for their parser. It's a bit naive but it handles the majority of cases.
  • Domino
    Domino over 7 years
    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.
  • Domino
    Domino over 7 years
    On a "funny" sidenote, you can make all functions act as if they were anonymous built-ins by running this: Function.prototype.toString = function () { return 'function () { [native code] }'; };
  • James Drew
    James Drew over 7 years
    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.
  • James Drew
    James Drew over 7 years
    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! 🤔
  • Avinash
    Avinash about 7 years
    With ES6 arrow functions having single argument like (a => a*10) it fails to give desired output.
  • Robin F.
    Robin F. about 7 years
    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)
  • myzhou
    myzhou about 7 years
    That example is just to get the parameters' name for you.
  • chharvey
    chharvey over 6 years
    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?
  • George 2.0 Hope
    George 2.0 Hope over 6 years
    The while loop results in an infinite loop with the code provided (as of 20171121at1047EDT)
  • Ates Goral
    Ates Goral over 6 years
    @George2.0Hope Thanks for pointing that out. I'll update the answer.
  • George 2.0 Hope
    George 2.0 Hope over 6 years
    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));
  • Ates Goral
    Ates Goral over 6 years
    Things have changed quite a bit since 2009!
  • Hearen
    Hearen over 6 years
    @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.
  • balupton
    balupton over 6 years
    function gotcha (a, b = false, c) {}; alert(gotcha.length)
  • Magnus
    Magnus about 6 years
    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?
  • Individual11
    Individual11 about 6 years
    in case anyone shows up here for React, this function, which is awesome, will not work in strict mode.
  • Michael Theriot
    Michael Theriot almost 6 years
    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.
  • pwilcox
    pwilcox almost 6 years
    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.
  • cue8chalk
    cue8chalk over 5 years
    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.
  • Dale Anderson
    Dale Anderson over 5 years
    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!
  • Michael Auderer
    Michael Auderer almost 5 years
    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)
  • Tim Pozza
    Tim Pozza almost 5 years
    let var = [...arguments]... for param values, updated to reflect ES6 and later. Just sayin'.
  • Jack Allan
    Jack Allan almost 5 years
    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 to
  • Jack Allan
    Jack Allan almost 5 years
    (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.
  • eran otzap
    eran otzap over 4 years
    what about the values ?
  • charlie roberts
    charlie roberts over 4 years
    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!
  • A T
    A T over 4 years
    Doesn't work with this simple arrow function: f = (a, b) => void 0; on getParameters(f) I get TypeError: Cannot read property '1' of null
  • A T
    A T over 4 years
    Thanks, yours is the only one in this thread which worked for me.
  • ZomoXYZ
    ZomoXYZ over 4 years
    @AT I have just updated the answer to fix support for your issue
  • A T
    A T over 4 years
    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/29123804
  • Dmitri
    Dmitri over 4 years
    Does not work when function has default values (role, name="bob") The extracted parameter is name="bob" instead of expected "name"
  • Jelle De Loecker
    Jelle De Loecker about 4 years
    This will also not work when there are default string values that contain "//" or "/*"
  • Dan O
    Dan O over 3 years
    seems to fail for inline comments containing a close-paren, e.g. function foo(x, y /* FIXME: (temp), z */, w) {
  • Michael A. Vickers
    Michael A. Vickers over 3 years
    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.
  • Kai Lehmann
    Kai Lehmann over 3 years
    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 :)
  • Melab
    Melab about 3 years
    Does it put a list of a function's parameter names inside the function?
  • SanBen
    SanBen about 3 years
    @Melab getMetadata('design:paramtypes', x) will only return the types, as an example [String]
  • S.Serpooshan
    S.Serpooshan almost 3 years
    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){} }
  • Mike 'Pomax' Kamermans
    Mike 'Pomax' Kamermans over 2 years
    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 ;)
  • Zack Morris
    Zack Morris over 2 years
    @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!