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!

Jonathan Chan
  • 2,355
  • 3
  • 25
  • 38
  • 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. – Winfield Trail Aug 03 '11 at 04: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. –  Aug 03 '11 at 04:23
  • @sudowned :/ I'm not debugging, reflection is an integral part in the design of my application... – Jonathan Chan Aug 03 '11 at 04:40
  • @pst Unfortunately I have to be able to get the arguments outside of the body of the function - I'm updating the question. – Jonathan Chan Aug 03 '11 at 04:41
  • Rather late, but apparently the term I was looking for was parameters. I found out a (rather convoluted) way to get those: http://jsbin.com/ucacit. – Jonathan Chan Jan 03 '12 at 04:00

8 Answers8

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.

HBP
  • 15,685
  • 6
  • 28
  • 34
  • 1
    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. – GregL Jun 26 '13 at 00:23
  • 1
    Thanks for that, in my own coding I always put in that space. I've updated the code. – HBP Jun 26 '13 at 03:25
  • 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.) – Brian McCutchon Jul 15 '13 at 16:30
  • 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. – HBP Jul 15 '13 at 19:46
  • 1
    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. `()`) and then matching everything that is not a bracket, comma, or whitespace within those brackets. – Nadeem Douba Jul 09 '15 at 17:25
  • @nadeem Re my comment of 15 July '13 above. The value returned by the toString function applied to a `function` is not fully specified. It may be a "sanitized" version or the original source text. In the latter case, it is possible for comments to be present. The extra checking in the regular expression reduces the chances of an erroneous match in those cases. – HBP Jul 10 '15 at 06:17
  • Thanks for the answer. This doesn't work if you have a new-line in your signature though. The solution by @Etienne does http://stackoverflow.com/a/13660631/653799 – Juan Campa Aug 07 '16 at 00:03
  • @HBP how about [arrow functions](https://www.sitepoint.com/es6-arrow-functions-new-fat-concise-syntax-javascript/)? – Kiril May 16 '17 at 14:25
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 )

Etienne
  • 16,249
  • 3
  • 26
  • 31
  • 1
    What is the purpose of capturing the parameter underscore separately and ignoring it? – Carl G Jun 15 '13 at 09:51
  • Great solution. I don't get why if I grab functions from `this` I cannot apply to it, see `getAllFunctions` by @kooinc in http://stackoverflow.com/questions/11279441/return-all-of-the-functions-that-are-defined-in-a-javascript-file and the comment below. – loretoparisi Mar 16 '16 at 11:01
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

wong2
  • 34,358
  • 48
  • 134
  • 179
  • 2
    I think the `.length` property is the best you can do. After all, who cares what the names of the parameters are? – Ray Toal Aug 03 '11 at 05:21
  • Sorry for the poor wording, but this is what I intended to do. Thanks! :) – Jonathan Chan Aug 03 '11 at 06:07
  • caveat: if the function is defined with the [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax), it `length` will return 0 (see e.g., `(function(...args) { return args; }).length`) – toraritte Jan 29 '22 at 21:47
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(',')
}
Tito100
  • 1,660
  • 1
  • 12
  • 12
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.

nucleartide
  • 3,888
  • 4
  • 28
  • 29
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){}));
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....

Ray Toal
  • 86,166
  • 18
  • 182
  • 232
-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.

xudifsd
  • 770
  • 5
  • 26