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

假设我的函数是这样的

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

其他回答

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;
}

无论哪种解决方案,它都不能破坏奇怪的函数,其toString()看起来也很奇怪:

function  (  A,  b
,c      ,d
){}

另外,为什么要使用复杂的正则表达式?可以这样做:

function getArguments(f) {
    return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}

这适用于每个函数,唯一的正则表达式是删除空白,由于.split技巧,它甚至不处理整个字符串。

好了,这是个老问题,有很多足够的答案。 下面是我提供的不使用正则表达式的产品,除了去除空白的琐碎任务。(我应该注意到,“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);
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=> ["a", "b", "c"]

我知道这是一个老问题,但初学者一直在复制从函数的字符串表示中提取参数名的解决方案,就好像这是任何代码中的良好实践一样。大多数情况下,这只是隐藏了逻辑中的缺陷。

在函数声明的圆括号中写入参数名可以看作是创建变量的一种简写语法。这样的:

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”,这一点用都没有。