是否有一种方法可以动态地获取函数的函数参数名?
假设我的函数是这样的
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
}
现在,我如何从函数内部获得参数名称及其值的列表到数组中?
当前回答
我知道这是一个老问题,但初学者一直在复制从函数的字符串表示中提取参数名的解决方案,就好像这是任何代码中的良好实践一样。大多数情况下,这只是隐藏了逻辑中的缺陷。
在函数声明的圆括号中写入参数名可以看作是创建变量的一种简写语法。这样的:
function doSomething(foo, bar) {
console.log("does something");
}
...类似于这个:
function doSomething() {
var foo = arguments[0];
var bar = arguments[1];
console.log("does something");
}
变量本身存储在函数的作用域中,而不是作为对象中的属性。就像您不能用代码操作变量的名称一样,也无法检索参数的名称,因为它不是字符串,并且可以在JIT编译期间消除它。
我一直认为函数的字符串表示形式是调试目的的工具,特别是因为这个参数类数组对象。首先,您不需要为参数指定名称。如果您尝试解析一个字符串化函数,它实际上不会告诉您它可能接受的额外未命名参数。
还有一种更糟糕、更常见的情况。如果一个函数有超过3或4个参数,那么向它传递一个更容易处理的对象可能是合乎逻辑的。
function saySomething(obj) {
if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}
saySomething({sender: "user123", message: "Hello world"});
在这种情况下,函数本身将能够读取它接收到的对象,并查找其属性并获得它们的名称和值,但试图解析函数的字符串表示只能为参数提供“obj”,这一点用都没有。
其他回答
我知道这是一个老问题,但初学者一直在复制从函数的字符串表示中提取参数名的解决方案,就好像这是任何代码中的良好实践一样。大多数情况下,这只是隐藏了逻辑中的缺陷。
在函数声明的圆括号中写入参数名可以看作是创建变量的一种简写语法。这样的:
function doSomething(foo, bar) {
console.log("does something");
}
...类似于这个:
function doSomething() {
var foo = arguments[0];
var bar = arguments[1];
console.log("does something");
}
变量本身存储在函数的作用域中,而不是作为对象中的属性。就像您不能用代码操作变量的名称一样,也无法检索参数的名称,因为它不是字符串,并且可以在JIT编译期间消除它。
我一直认为函数的字符串表示形式是调试目的的工具,特别是因为这个参数类数组对象。首先,您不需要为参数指定名称。如果您尝试解析一个字符串化函数,它实际上不会告诉您它可能接受的额外未命名参数。
还有一种更糟糕、更常见的情况。如果一个函数有超过3或4个参数,那么向它传递一个更容易处理的对象可能是合乎逻辑的。
function saySomething(obj) {
if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}
saySomething({sender: "user123", message: "Hello world"});
在这种情况下,函数本身将能够读取它接收到的对象,并查找其属性并获得它们的名称和值,但试图解析函数的字符串表示只能为参数提供“obj”,这一点用都没有。
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;
}
哇,已经有这么多答案了。我很确定这件事会被埋葬。即便如此,我觉得这对一些人来说可能有用。
我对所选的答案并不完全满意,因为在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提取值变得非常复杂。
如果这对你有用,请让我知道!很乐意听到一些反馈!
这是我的解决方案——它适用于命名函数和未命名函数,异步和非异步函数,异步和非异步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 !== "");
}
正确的方法是使用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()来实现。