如何动态地获得函数参数名/值?

是否有一种方法可以动态地获取函数的函数参数名?

假设我的函数是这样的

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

现在,我如何从函数内部获得参数名称及其值的列表到数组中?

252043 次浏览

我不知道如何得到参数列表但是你可以这样做来得到它期望的数量。注意,这只计算签名中没有默认值的参数:

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


console.log(foobar.length); // prints 3
console.log(foobar2.length); // prints 1

我以前试过这样做,但从来没有找到一个实用的方法来完成它。我最终传递了一个对象,然后循环遍历它。

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

我不知道这个解决方案是否适合您的问题,但它允许您重新定义任何您想要的函数,而无需更改使用它的代码。现有调用将使用定位参数,而函数实现可能使用“命名参数”(单个散列参数)。

我认为你无论如何都会修改现有的函数定义,所以,为什么不拥有一个工厂函数,让你想要的东西:

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

希望能有所帮助。

你可以使用"arguments"属性访问传递给函数的参数值。

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

这里有一种方法:

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

注意:这只适用于支持arguments.callee的浏览器。

下面的函数将返回传入的任何函数的参数名称的数组。

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

使用示例:

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

编辑:

随着ES6的发明,这个函数可以被默认参数绊倒。这里有一个在大多数情况下都有效的快速技巧:

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

我说大多数情况是因为有些事情会出错

function (a=4*(5/3), b) {} // returns ['a']
< p > 编辑: 我还注意到vikasde也想要数组中的参数值。这已经在名为arguments的局部变量中提供了。< / p >

摘录自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments:

arguments对象不是数组。它类似于Array,但除了长度之外没有任何Array属性。例如,它没有pop方法。然而,它可以转换为一个真正的数组:

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

如果Array泛型可用,则可以使用以下方法:

var args = Array.slice(arguments);

我通常是怎么做的:

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

你甚至可以通过函数名引用参数,比如:

name.arguments;

希望这能有所帮助!

下面是来自AngularJS的代码,它在依赖注入机制中使用了这种技术。

下面是来自http://docs.angularjs.org/tutorial/step_05的一个解释

Angular的依赖注入器为你的控制器提供服务 当控制器被构造时。依赖注入器 负责创建服务可能的任何传递依赖项 Have(服务通常依赖于其他服务).

注意,参数的名称是有意义的,因为注入器

/**
* @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;
}
//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

这很简单。

首先有一个已弃用的arguments.callee -一个被调用函数的引用。 其次,如果你有一个对函数的引用,你可以很容易地得到它们的文本表示。 第三,如果你把你的函数作为构造函数调用,你也可以通过youobject .constructor有一个链接。 注:第一个解决方案已弃用,所以如果你不能不使用它,你也必须考虑你的应用架构。 如果你不需要确切的变量名,只需在函数内部变量arguments中使用,而不需要任何魔法

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

它们都将调用toString并将其替换为re,这样我们就可以创建一个helper:

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

一些例子:

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

享受与JS!

UPD:实际上杰克·艾伦提供了一个更好的解决方案。GJ杰克!

空格和注释不容易出错的解决方案是:

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


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


["hi", "you"]
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

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

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

您还可以使用“esprima”解析器来避免参数列表中的注释、空格和其他问题。

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

它甚至可以用这样的代码:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]

从@jack-allan中获取回答,我稍微修改了函数,以允许ES6的默认属性,如:

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

仍然返回[ '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;
}
}

无论哪种解决方案,它都不能破坏奇怪的函数,其toString()看起来同样奇怪:

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

截图from console

另外,为什么要使用复杂的正则表达式?可以这样做:

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

这适用于每个函数,唯一的正则表达式是空格删除,由于.split技巧,它甚至不处理整个字符串。

下面是一个更新的解决方案,试图以一种紧凑的方式解决上述所有边缘情况:

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

简化的测试输出(完整的测试用例附在下面):

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

函数参数字符串值图像动态从JSON。因为项目。product_image2是一个URL字符串,当你在参数内部调用changeImage时,你需要把它放在引号中。

我的功能

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

我的函数

<script type="text/javascript">
function changeImage(img)
{
document.getElementById("saleDetailDivGetImg").src=img;
alert(img);
}
</script>

我知道这是一个老问题,但初学者一直在复制从函数的字符串表示中提取参数名的解决方案,就好像这是任何代码中的良好实践一样。大多数情况下,这只是隐藏了逻辑中的缺陷。

在函数声明的圆括号中写入参数名可以看作是创建变量的一种简写语法。这样的:

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

...类似于这个:

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


console.log("does something");
}

变量本身存储在函数的作用域中,而不是作为对象中的属性。就像您不能用代码操作变量的名称一样,也无法检索参数的名称,因为它不是字符串,并且可以在JIT编译期间消除它。

我一直认为函数的字符串表示形式是调试目的的工具,特别是因为这个arguments类数组对象。首先,您不需要为参数指定名称。如果您尝试解析一个字符串化函数,它实际上不会告诉您它可能接受的额外未命名参数。

还有一种更糟糕、更常见的情况。如果一个函数有超过3或4个参数,那么向它传递一个更容易处理的对象可能是合乎逻辑的。

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


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

在这种情况下,函数本身将能够读取它接收到的对象,并查找其属性并获得它们的名称和值,但试图解析函数的字符串表示只会给你"obj"对于参数,这一点用都没有。

这个问题的答案需要3个步骤:

  1. 获取传递给函数的实际参数值(让我们称其为argValues)。这是直接的,因为它将在函数中作为arguments可用。
  2. 从函数签名(让我们称其为argNames)中获取参数名。这并不容易,需要解析函数。不用自己处理复杂的正则表达式,也不用担心边界情况(默认参数、注释……),可以使用像babylon这样的库,它将函数解析为一个抽象语法树,从中可以获得参数的名称。
  3. 最后一步是将两个数组连接到一个数组中,该数组具有所有参数的名称和值。

代码是这样的

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)

日志对象将为

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

下面是一个工作示例https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

好吧,这是个老问题,有很多足够的答案。 下面是我提供的不使用正则表达式的产品,除了去除空白的琐碎任务。(我应该注意到,“strips_comments”函数实际上是将它们分隔开来,而不是从物理上删除它们。这是因为我在其他地方使用它,由于各种原因需要原始非注释令牌的位置保持完整)

这是一个相当长的代码块,因为这个粘贴包含一个迷你测试框架。

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

我已经阅读了这里的大部分答案,我想加上我的一句话。

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

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

或ECMA6中的单行函数

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

__

假设有一个函数

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

下面的代码将返回"abc,def,ghi,jkl"

该代码也可以用于卡米洛·马丁给出的函数的设置:

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

还有Bubersson对杰克·艾伦的回答的评论:

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

__

解释

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

这将使用new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')创建一个正则表达式。我必须使用new RegExp,因为我将一个变量(Function.name,目标函数的名称)注入到RegExp中。

例子如果函数名是"foo"(function foo()), RegExp将是/foo\s*\((.*?)\)/

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

然后它将整个函数转换为一个字符串,并删除所有换行符。删除换行符有助于卡米洛·马丁给出的函数设置。

.exec(...)[1]

这是RegExp.prototype.exec函数。它基本上将正则指数(new RegExp())匹配到字符串(Function.toString())。然后[1]将返回正则指数((.*?))中找到的第一个捕获组

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

这将删除/**/中的所有注释,并删除所有空格。


这现在也支持读取和理解箭头(=>)函数,例如f = (a, b) => void 0;,其中Function.toString()将返回(a, b) => void 0而不是普通函数的function f(a, b) { return void 0; }。原来的正则表达式会在混乱中抛出一个错误,但现在已经解决了这个问题。

变化从new RegExp(Function.name+'\\s*\\((.*?)\\)') (/Function\s*\((.*?)\)/)到new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') (/(?:Function\s*|^)\((.*?)\)/)


如果你想让所有的参数成为一个数组,而不是一个由逗号分隔的字符串,在结尾添加.split(',')

哇,已经有这么多答案了。我很确定这件事会被埋葬。即便如此,我觉得这对一些人来说可能有用。

我对所选的答案并不完全满意,因为在ES6中,默认值不能很好地工作。它也不提供默认值信息。我还想要一个不依赖于外部库的轻量级函数。

这个函数对于调试非常有用,例如:记录被调用函数的参数、默认参数值和参数。

我昨天花了一些时间在这个问题上,破解正确的RegExp来解决这个问题,这就是我想到的。它运行得非常好,我对结果非常满意:

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

正如你所知道的,一些参数名消失了,因为Babel转译器将它们从函数中删除了。如果你在最新的NodeJS中运行它,它会像预期的那样工作(注释的结果来自NodeJS)。

另一个注意事项,正如注释中所述,它不能作为默认值与内联箭头函数一起工作。这使得使用RegExp提取值变得非常复杂。

如果这对你有用,请让我知道!很乐意听到一些反馈!

这里的很多答案都使用正则表达式,这很好,但它不能很好地处理语言的新添加(如箭头函数和类)。同样值得注意的是,如果你在简化的代码上使用这些函数,它将进入🔥。它将使用任何简化后的名称。Angular通过允许你在向DI容器注册参数时传入一个与参数顺序匹配的有序字符串数组来解决这个问题。下面是解决方案:

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

这处理了原始的解析问题和更多的函数类型(例如箭头函数)。以下是它能处理什么和不能处理什么:

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

这取决于你想把它用于ES6代理和解构可能是你最好的选择。例如,如果你想将它用于依赖注入(使用参数的名称),那么你可以这样做:

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

它不是最先进的解析器,但它提供了一个想法,你可以使用代理来处理它,如果你想使用简单的DI args解析器。然而,在这种方法中有一个小小的警告。我们需要使用解构赋值而不是普通的参数。当我们传入注入器代理时,析构与调用对象的getter是一样的。

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

输出如下:

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

它连接了整个应用程序。最好的一点是,应用程序很容易测试(你可以只实例化每个类,并传入模拟/存根/等)。此外,如果您需要交换实现,您可以从一个地方完成。这一切都是因为JS Proxy对象。

注意:在它准备好投入生产使用之前,还有很多工作要做,但它确实给出了它看起来像什么的想法。

这个答案有点晚了,但它可能会帮助其他有同样想法的人。👍

下面我举个简单的例子:

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

这个包使用重铸来创建AST,然后从它们收集参数名,这允许它支持模式匹配,默认参数,箭头函数和其他ES6特性。

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

我修改了来自AngularJS的版本,它实现了一个依赖注入机制,可以在没有Angular的情况下工作。我还更新了STRIP_COMMENTS正则表达式,以便与ECMA6一起工作,因此它支持签名中的默认值等内容。

var FN_ARGS = /^function\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({})

注意:如果你想使用ES6参数解构与顶部解决方案添加下面的行。

if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)

由于JavaScript是一种脚本语言,我认为它的内省应该支持获取函数参数名。在这个功能上下注违反了基本原则,所以我决定进一步探讨这个问题。

这让我找到了这个问题,但没有内置的解决方案。这让我找到了这个答案,它解释了arguments只是被弃用的函数,所以我们不能再使用myFunction.arguments,否则我们会得到:

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

是时候卷起袖子开始工作了:

⭐检索函数参数需要解析器,因为像4*(5/3)这样的复杂表达式可以用作默认值。所以Gaafar的回答詹姆斯·德鲁的回答是目前为止最好的方法。

我尝试了巴比伦esprima解析器,但不幸的是,正如Mateusz Charytoniuk的回答中指出的那样,它们不能解析独立的匿名函数。我想出了另一个解决办法,把代码括在括号里,这样就不会改变逻辑:

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

换行符防止了//(单行注释)的问题。

⭐如果解析器不可用,次优选择是使用一种可靠的技术,比如Angular.js的依赖注入器正则表达式。我将Lambder的回答的函数版本与humbletim的回答结合起来,并添加了一个可选的ARROW布尔值,用于控制正则表达式是否允许ES6胖箭头函数。


这里有两个解决方案。注意,它们没有检测函数是否具有有效语法的逻辑,它们只提取参数。这通常是可以的,因为我们通常将解析函数传递给getArguments(),因此它们的语法已经有效。

我将尽我所能管理这些解决方案,但如果没有JavaScript维护者的努力,这将仍然是一个悬而未决的问题。

Node.js版本(在StackOverflow支持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() {}');

完整的工作示例:

< a href = " https://repl。它/ repl SandybrownPhonyAngles noreferrer“rel = > https://repl.it/repls/SandybrownPhonyAngles < / >

浏览器版本(注意它停在第一个复杂的默认值):

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

完整的工作示例:

< a href = " https://repl。它/ repl StupendousShowyOffices noreferrer“rel = > https://repl.it/repls/StupendousShowyOffices < / >

正确的方法是使用JS解析器。下面是一个使用橡子的例子。

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

这里的代码查找函数f的三个(形式)参数的名称。它通过将f送入acorn.parse()来实现。

尝试手动:

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

这一点还没有提到,如果你使用Typescript,你可以在使用decorator时发出元数据,这将允许你获得参数类型。

只有当类/函数/道具上有装饰器时,元数据才会被触发。

这个特性可以通过在tsconfig.json中设置emitDecoratorMetadata为true来启用

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

由于元数据仍然是早期的建议,所以必须安装或反射reflect-metadata包。getMetadata不会被定义。

npm install reflect-metadata

你可以这样使用它:

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

例如,在较新的Angular版本中,它被用来决定注入什么->https://stackoverflow.com/a/53041387/1087372

我想建议解决方案,支持箭头功能,如 我使用这篇文章的基本正则表达式和https://davidwalsh.name/javascript-arguments和添加箭头函数支持

(arg1,arg2) => {}

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

这是我的解决方案——它适用于命名函数和未命名函数,异步和非异步函数,异步和非异步lambdas,以及带和不带paren的lambdas。

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