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

假设我的函数是这样的

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

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


当前回答

这一点还没有提到,如果你使用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

其他回答

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

函数foobar(a, b, c) {} 函数foobar2(a, b=false, c=false) {} console.log (foobar.length);//打印3个 console.log (foobar2.length);//打印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
});

这很简单。

At the first there is a deprecated arguments.callee — a reference to called function. At the second if you have a reference to your function you can easily get their textual representation. At the third if you calling your function as constructor you can also have a link via yourObject.constructor. NB: The first solution deprecated so if you can't to not use it you must also think about your app architecture. If you don't need exact variable names just use inside a function internal variable arguments without any magic.

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

这个问题的答案需要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

由于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