29

Is it possible to get all of the arguments a Javascript function is written to accept? (I know that all Javascript function arguments are "optional")? If not, is it possible to get the number of arguments? For example, in PHP, one could use:

$class = new ReflectionClass('classNameHere');
$methods = $class->getMethods();
foreach ($methods as $method) {
    print_r($method->getParameters());
}

... or something like that, I haven't touched PHP in a while so the example above may not be correct.

Thanks in advance!

EDIT: Unfortunately, I have to be able to get the arguments outside of the body of the function... Sorry for the lack of clarification, but thanks for the current answers!

5
  • debugging anything with getParameters() just makes me so mad. i mean, that's not helpful, i'm just saying is all. maybe there's a better way of solving your problem. Commented Aug 3, 2011 at 4:23
  • Sure there is. Through the arguments keyword (inside a function it is an array-like object were arguments[0] is the first argument, etc). This isn't an answer because I am too lazy to go find a good reference. However, this only exposes the values, not the names. Depending upon a particular "To String" implementation for a Function, the names can be extracted via that and parsing magic. Commented Aug 3, 2011 at 4:23
  • @sudowned :/ I'm not debugging, reflection is an integral part in the design of my application... Commented Aug 3, 2011 at 4:40
  • @pst Unfortunately I have to be able to get the arguments outside of the body of the function - I'm updating the question. Commented Aug 3, 2011 at 4:41
  • Rather late, but apparently the term I was looking for was parameters. I found out a (rather convoluted) way to get those: jsbin.com/ucacit. Commented Jan 3, 2012 at 4:00

9 Answers 9

46

This new version handles fat arrow functions as well...

args = f => f.toString ().replace (/[\r\n\s]+/g, ' ').
              match (/(?:function\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/).
              slice (1,3).
              join ('').
              split (/\s*,\s*/);

function ftest (a,
                 b,
                 c) { }

let aftest = (a,
                 b,
                 c) => a + b / c;

console.log ( args (ftest),  // = ["a", "b", "c"] 
              args (aftest), // = ["a", "b", "c"]
              args (args)    // = ["f"]
             );

Here is what I think you are looking for :

 function ftest (a,
                 b,
                 c) { }
 var args = ftest.toString ().
              replace (/[\r\n\s]+/g, ' ').
              match (/function\s*\w*\s*\((.*?)\)/)[1].split (/\s*,\s*/);

args will be an array of the names of the arguments of test i.e. ['a', 'b', 'c']

The value is args will be an array of the parameter names if the ftest is a function. The array will be empty if ftest has not parameters. The value of args will be null if ftest fails the regular expression match, i.e it is not a function.

Sign up to request clarification or add additional context in comments.

8 Comments

One small flaw in your regex that the Angular one in @Etienne's answer addresses is the first \s+ will prevent matching anonymous functions with no space between the keyword and the brackets, i.e. function(x,y) { /* ... */ }. This could be avoided by changing it to a \s* instead.
Thanks for that, in my own coding I always put in that space. I've updated the code.
The error that GregL pointed out in your first example also occurs (in a slightly different form) in your second. The regex should probably read /^\s*function(?:\s+\w*)?\s*\(([\s\S]*?)\)/. (I also made it allow for multi-line argument lists.)
Thanks for the report. Function.toString does not return the source as you typed it (at least on browsers I have tested). but returns a reformatted version so some of these changes may not be strictly speaking necessary. To make it absolutely foolproof you would need to cater of comments wherever whitespace is valid.
Why are you complicating the regex so much? Why not just do this: ftest.toString().match(/\([^\)]*\)/m)[0].match(/[^\n\r\s,\)\(]/g). It is basically matching everything between the first pair of round brackets (i.e. (<content>)) and then matching everything that is not a bracket, comma, or whitespace within those brackets.
|
18

it is possible get all the formal parameter name of a javascript:

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

function formalParameterList(fn) {
   var fnText,argDecl;
   var args=[];
   fnText = fn.toString().replace(STRIP_COMMENTS, '');
   argDecl = fnText.match(FN_ARGS); 

   var r = argDecl[1].split(FN_ARG_SPLIT);
   for(var a in r){
      var arg = r[a];
      arg.replace(FN_ARG, function(all, underscore, name){
         args.push(name);
      });
   }
   return args;
 }

this can be tested this way :

 var expect = require('expect.js');
 expect( formalParameterList(function() {} )).to.eql([]);
 expect( formalParameterList(function () {} )).to.eql([]);
 expect( formalParameterList(function /*  */ () {} )).to.eql([]);
 expect( formalParameterList(function (/* */) {} )).to.eql([]);
 expect( formalParameterList(function ( a,   b, c  ,d /* */, e) {} )).to.eql(['a','b','c','d','e']);

Note: This technique is use with the $injector of AngularJs and implemented in the annotate function. (see https://github.com/angular/angular.js/blob/master/src/auto/injector.js and the corresponding unit test in https://github.com/angular/angular.js/blob/master/auto/injectorSpec.js )

2 Comments

What is the purpose of capturing the parameter underscore separately and ignoring it?
Great solution. I don't get why if I grab functions from this I cannot apply to it, see getAllFunctions by @kooinc in stackoverflow.com/questions/11279441/… and the comment below.
12

Suppose your function name is foo

Is it possible to get all of the arguments a Javascript function is written to accept?

arguments[0] to arguments[foo.length-1]

If not, is it possible to get the number of arguments?

foo.length would work

3 Comments

I think the .length property is the best you can do. After all, who cares what the names of the parameters are?
Sorry for the poor wording, but this is what I intended to do. Thanks! :)
caveat: if the function is defined with the spread syntax, it length will return 0 (see e.g., (function(...args) { return args; }).length)
2

check only required chars. with func.toString().regex you checked full length.so if function is class with 500 lines of code...

function getParams(func){
    var str=func.toString();
    var len = str.indexOf("(");
    return str.substr(len+1,str.indexOf(")")-len -1).replace(/ /g,"").split(',')
}

Comments

2

HBP's answer is what most people are looking for, but if you're the one defining the function, you can also assign a property to the function object. For example,

a.arguments = ['foo', 'bar', 'baz']
function a(foo, bar, baz) {
  // do stuff
}

This is debatably more clear, but you'll have to write your arguments twice.

Comments

1

Now when you say outside the body of the function I can only imagine that you want to know what the names of the parameters are? Because as far as the values go, you already know what arguments you are passing. Other answers have said you can get the length of the function, which is the number of parameters it explicitly declares. Now if you want to know the names outside the function, how about the toString hack?

Consider

function f(oh, hi, there) {
    return hi + there / oh;
}

Then

alert(f);

What do you see? RIght, just regex them out! Okay, SORRY to bring this up. Perhaps it is not standard ECMAScript, but it, uh, works in Chrome....

Comments

1

args = f => f.toString ()
    .replace( /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,'')
    .replace(/(\r\n\t|\n|\r\t)/gm,"")
    .trim()
    .match (/(?:\w*?\s?function\*?\s?\*?\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/)
    .slice (1,3)
    .join ('').replace(/\s/g, '').
    split (/\s*,\s*/);

/*Test*/
console.log(args((a,b)=>a+b));
console.log(args(function(c,d){return c+d;}));
console.log(args(async function(a,b,c){/**/}));
console.log(args(function* (a,b,c,d){/**/}));
console.log(args(function name(s1,s2){}));
console.log(args(function name(/*comment 1*/ s3/*comment2*/,s4//
){}));
console.log(args(async function* name(/*comment1*/ s5/*comment2*/,s6){}));

console.log(args(async function * name(/*comment1*/ s7/*comment2*/,s8){}));

console.log(args(async function *name(/*comment1*/ s9/*comment2*/,s10){}));

Comments

0

Here is my method for this use-case:

Grabbing the top level parameters of functional code blocks ie: lambda expressions and functions, named and anonymous.

If this is what you're wanting to extract from:

({ device: { name, brand }, settings }, { brightness = 50, volume = 70 }) => {};

And you are looking for this: ['{ device: { name,brand },settings }', '{ brightness = 50,volume = 70 }']

Then you can utilize this:

function getFirstLayerParams(func) {
  const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
  const paramSectionRegex = /\(([^)]+)\)/;
  let fnString = func.toString();
  fnString = fnString.replace(STRIP_COMMENTS, '');
  const paramsMatch = fnString.toString().match(paramSectionRegex);
  const theParams = [];
  if (paramsMatch) {
    const paramsString = paramsMatch[1];
    const params = paramsString.split(',').map((param) => param.trim());
    let depth = 0;
    let currentParam = '';
    for (const param of params) {
      currentParam += param;
      depth += (param.match(/{/g) || []).length - (param.match(/}/g) || []).length;
      if (depth === 0 && currentParam.trim().length > 0) {
        theParams.push(currentParam.trim());
        currentParam = '';
      } else {
        currentParam += ',';
      }
    }
  }
  return theParams;
}

As of right now, this will not work if you include defaults of functions/expressions ie: errorHandler = () => console.error('Error occurred') } Of course, resolving this last bit might not be that difficult, as you can just walk the parameters, and as you encounter them, identify the content to be a function of some sort and go from there. I'm not doing that because I don't expect to ever have defaults of function bodies, and I don't want to pull my hair out right now.

This came about after doing research of my own in order to build my approach to lift required parameter signatures like we need in test frameworks such as RTL and Playwright, I saw that maybe, I could call 'use' for fixtures on behalf of the developer (and to enable other features of my own) to shift the requirement to only passing fixture dependency parameters.

const funcArgs = getFirstLayerParams(func);
const paramsResult = funcArgs[0];
const paramSection = `${paramsResult ?? '{}'},`;
const nestedFunctionParamResult = funcArgs[0] ?? '{}'
const useThisFunc = new Function(
      `const func = arguments[0]; return (${paramSection} use) => { use(func(${nestedFunctionParamResult}));};`,
    ).bind(null, func)();

This is the basic aspect. I added more, such as injecting other parameters that weren't specified in implementation by inserting right before the final closing curly brace.

Also, if you want to extract the function body, all you have to do is take this code, and find the last index of characters where the parameter section would stop, then slice whatever you need.

Comments

-1

JavaScript is a dialects of ECMAScript, according to ECMAScript standard, a function is also a object, and when a function is called, function can access arguments object, this arguments is array-like object, it has length property, so you can use arguments.length to traverse all arguments passed to this function. visit http://interglacial.com/javascript_spec/a-13.html#a-13.2.1 for more details.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.