是否有一种方法可以动态地获取函数的函数参数名?
假设我的函数是这样的
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
}
现在,我如何从函数内部获得参数名称及其值的列表到数组中?
当前回答
由于JavaScript是一种脚本语言,我认为它的内省应该支持获取函数参数名。在这个功能上下注违反了基本原则,所以我决定进一步探讨这个问题。
这让我想到了这个问题,但没有内置的解决方案。这让我得到了这个答案,它解释了参数只在函数之外被弃用,所以我们不能再使用myFunction。或者我们得到:
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的答案或者James Drew的答案是目前为止最好的方法。
我尝试了巴比伦和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() {}');
完整的工作示例:
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() {}');
完整的工作示例:
https://repl.it/repls/StupendousShowyOffices
其他回答
我修改了来自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({})
无论哪种解决方案,它都不能破坏奇怪的函数,其toString()看起来也很奇怪:
function ( A, b
,c ,d
){}
另外,为什么要使用复杂的正则表达式?可以这样做:
function getArguments(f) {
return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}
这适用于每个函数,唯一的正则表达式是删除空白,由于.split技巧,它甚至不处理整个字符串。
您还可以使用“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"]
你可以使用"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]);
这一点还没有提到,如果你使用Typescript,你可以在使用decorator时发出元数据,这将允许你获得参数类型。
元数据只有在类/函数/道具上有装饰器时才会被触发。 这与哪个装饰器无关。
可以通过在tsconfig.json中将emitDecoratorMetadata设置为true来启用该特性
{
"compilerOptions": {
"emitDecoratorMetadata": true
}
}
由于元数据仍然是一个早期的建议,所以必须安装或反射反射元数据包。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