是否有一种方法可以动态地获取函数的函数参数名?
假设我的函数是这样的
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
}
现在,我如何从函数内部获得参数名称及其值的列表到数组中?
当前回答
你可以使用"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]);
其他回答
我不知道这个解决方案是否适合您的问题,但它允许您重新定义任何您想要的函数,而无需更改使用它的代码。现有调用将使用定位参数,而函数实现可能使用“命名参数”(单个散列参数)。
我认为你无论如何都会修改现有的函数定义,所以,为什么不拥有一个工厂函数,让你想要的东西:
<!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>
希望能有所帮助。
无论哪种解决方案,它都不能破坏奇怪的函数,其toString()看起来也很奇怪:
function ( A, b
,c ,d
){}
另外,为什么要使用复杂的正则表达式?可以这样做:
function getArguments(f) {
return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}
这适用于每个函数,唯一的正则表达式是删除空白,由于.split技巧,它甚至不处理整个字符串。
由于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
这一点还没有提到,如果你使用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
这个包使用重铸来创建AST,然后从它们收集参数名,这允许它支持模式匹配,默认参数,箭头函数和其他ES6特性。
https://www.npmjs.com/package/es-arguments