是否有一种方法可以动态地获取函数的函数参数名?
假设我的函数是这样的
function doSomething(param1, param2, .... paramN){
// fill an array with the parameter name and value
// some other code
}
现在,我如何从函数内部获得参数名称及其值的列表到数组中?
是否有一种方法可以动态地获取函数的函数参数名?
假设我的函数是这样的
function doSomething(param1, param2, .... paramN){
// fill an array with the parameter name and value
// some other code
}
现在,我如何从函数内部获得参数名称及其值的列表到数组中?
当前回答
这个问题的答案需要3个步骤:
To get the values of the actual parameters passed to the function (let's call it argValues). This is straight forward as it will be available as arguments inside the function. To get the parameter names from the function signature (let's call it argNames). This not as easy and requires parsing the function. Instead of doing the complex regex yourself and worrying about edge cases (default parameters, comments, ...), you can use a library like babylon that will parse the function into an abstract syntax tree from which you can obtain the names of parameters. The last step is to join the 2 arrays together into 1 array that has the name and value of all the parameters.
代码是这样的
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
其他回答
哇,已经有这么多答案了。我很确定这件事会被埋葬。即便如此,我觉得这对一些人来说可能有用。
我对所选的答案并不完全满意,因为在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提取值变得非常复杂。
如果这对你有用,请让我知道!很乐意听到一些反馈!
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;
}
好了,这是个老问题,有很多足够的答案。 下面是我提供的不使用正则表达式的产品,除了去除空白的琐碎任务。(我应该注意到,“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);
下面是来自AngularJS的代码,它在依赖注入机制中使用了这种技术。
以下是来自http://docs.angularjs.org/tutorial/step_05的解释
Angular的依赖注入器为你的控制器提供服务 当控制器被构造时。依赖注入器 负责创建服务可能的任何传递依赖项 拥有(服务通常依赖于其他服务)。 注意,参数的名称是重要的,因为注入器 使用这些来查找依赖项。
/**
* @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;
}
下面是一个更新的解决方案,试图以一种紧凑的方式解决上述所有边缘情况:
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>