【问题标题】:How to get function parameter names/values dynamically?如何动态获取函数参数名称/值?
【发布时间】:2010-11-03 17:25:11
【问题描述】:

有没有办法动态获取函数的函数参数名称?

假设我的函数如下所示:

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

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

【问题讨论】:

  • 感谢大家。搜索后,我在 SO 上找到了解决方案:stackoverflow.com/questions/914968/… 它使用正则表达式来获取参数名称。它可能不是最好的解决方案,但它对我有用。

标签: javascript reflection function-parameter


【解决方案1】:

由于尚未提及,如果您使用 Typescript,则可以在使用装饰器时发出元数据,这将允许您获取参数类型。

只有在类/函数/道具上有装饰器时才会发出元数据。
哪个装饰器无关紧要。

可以通过在 tsconfig.json 中将 emitDecoratorMetadata 设置为 true 来启用此功能

{
  "compilerOptions": {
    "emitDecoratorMetadata": true
  }
}

由于元数据仍然是早期的proposal,因此必须安装reflect-metadata 包,否则将不会定义 Reflect.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

【讨论】:

  • 它是否将函数的参数名称列表放入函数中?
  • @Melab getMetadata('design:paramtypes', x) 只会返回类型,例如 [String]
【解决方案2】:

我想建议支持箭头功能的解决方案,例如 我将这篇文章用于基本的正则表达式和https://davidwalsh.name/javascript-arguments,并添加了箭头函数支持

(arg1,arg2) => {}

arg => {}



function getArgs(func) {
  if(func.length === 0){
      return []
  }

  let string = func.toString();

  let args;
  // First match everything inside the function argument parens. like `function (arg1,arg2) {}` or `async function(arg1,arg2) {}


  args = string.match(/(?:async|function)\s*.*?\(([^)]*)\)/)?.[1] ||
      // arrow functions with multiple arguments  like `(arg1,arg2) => {}`
         string.match(/^\s*\(([^)]*)\)\s*=>/)?.[1] ||
      // arrow functions with single argument without parens like `arg => {}`
         string.match(/^\s*([^=]*)=>/)?.[1]

  // Split the arguments string into an array comma delimited.
  return args.split(',').map(function(arg) {
    // Ensure no inline comments are parsed and trim the whitespace.
    return arg.replace(/\/\*.*\*\//, '').trim();
  }).filter(function(arg) {
    // Ensure no undefined values are added.
    return arg;
  });
}

【讨论】:

  • 对于包含 close-paren 的内联 cmets 似乎失败,例如function foo(x, y /* FIXME: (temp), z */, w) {
  • 我尝试编辑但“建议的编辑队列已满”。您可以通过将返回语句中靠近底部的“replace(/\/*.**\//, '')”移动到“string = func.toString()”的末尾来解决 Dan O 报告的问题" 顶部附近的声明。确保将 trim() 保留在 return 语句中。
【解决方案3】:

我已经阅读了这里的大部分答案,我想添加我的单行。

new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)').exec(Function.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

function getParameters(func) {
  return new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
}

或者对于 ECMA6 中的单线函数

var getParameters = func => new RegExp('(?:'+func.name+'\\s*|^)\\s*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');

__

假设你有一个函数

function foo(abc, def, ghi, jkl) {
  //code
}

以下代码将返回"abc,def,ghi,jkl"

该代码还可以用于设置Camilo Martin 提供的函数:

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

还有布伯松对Jack Allan's answer的评论:

function(a /* fooled you)*/,b){}

__

说明

new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')

这将创建一个带有new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')Regular Expression。我必须使用new RegExp,因为我将一个变量(Function.name,目标函数的名称)注入到正则表达式中。

示例如果函数名是“foo”(function foo()),则正则表达式将为/foo\s*\((.*?)\)/

Function.toString().replace(/\n/g, '')

然后它将整个函数转换为字符串,并删除所有换行符。删除换行符有助于设置Camilo Martin 提供的功能。

.exec(...)[1]

这是RegExp.prototype.exec 函数。它基本上将正则指数 (new RegExp()) 匹配到字符串 (Function.toString()) 中。然后[1] 将返回在正则指数中找到的第一个Capture Group ((.*?))。

.replace(/\/\*.*?\*\//g, '').replace(/ /g, '')

这将删除/**/ 中的所有评论,并删除所有空格。


这现在还支持阅读和理解箭头 (=>) 函数,例如 f = (a, b) => void 0;,其中 Function.toString() 将返回 (a, b) => void 0 而不是普通函数的 function f(a, b) { return void 0; }。原来的正则表达式会在混乱中引发错误,但现在已经解决了。

变化是从new RegExp(Function.name+'\\s*\\((.*?)\\)') (/Function\s*\((.*?)\)/) 到 new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') (/(?:Function\s*|^)\((.*?)\)/)


如果你想把所有参数变成一个数组而不是一个用逗号分隔的字符串,最后只需添加.split(',')

【讨论】:

  • 相当不错。一行,处理箭头函数,没有外部依赖,并且涵盖了足够多的边缘情况,至少对于我的预期用途。如果您可以对您将要处理的函数签名做出一些合理的假设,这就是您所需要的。谢谢!
  • 不适用于这个简单的箭头函数:f = (a, b) => void 0;在getParameters(f) 我得到TypeError: Cannot read property '1' of null
  • @AT 我刚刚更新了答案以修复对您的问题的支持
  • 谢谢...但请记住,括号不再是必需的,因此您可以执行getParameters(a => b => c => d => a*b*c*d) 之类的操作,使用您的代码仍然可以提供TypeError: Cannot read property '1' of null... 而这个有效stackoverflow.com/a/29123804
  • 当函数有默认值时不起作用(role, name="bob") 提取的参数是name="bob" 而不是预期的"name"
【解决方案4】:

手动尝试:

function something(arg1, arg2) {
  console.log ( arg1 + arg2 );
}

【讨论】:

    【解决方案5】:

    正确的方法是使用 JS 解析器。这是使用acorn 的示例。

    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() 来实现。

    【讨论】:

    • 值呢?
    【解决方案6】:

    您还可以使用“esprima”解析器来避免参数列表中的 cmets、空格和其他内容的许多问题。

    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"]
    

    【讨论】:

      【解决方案7】:

      由于JavaScript是一种脚本语言,我觉得它的自省应该支持获取函数参数名。使用该功能违反了第一原则,因此我决定进一步探讨该问题。

      这让我找到了this question,但没有内置解决方案。这使我找到了this answer,它解释了arguments 仅在函数之外被弃用,所以我们不能再使用myFunction.arguments 或者我们得到:

      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's answerJames Drew's answer 是迄今为止最好的方法。

      我尝试了babylonesprima 解析器,但不幸的是它们无法解析独立的匿名函数,正如Mateusz Charytoniuk's answer 中所指出的那样。我通过将代码括在括号中找到了另一种解决方法,以免改变逻辑:

      const ast = parser.parse("(\n" + func.toString() + "\n)")
      

      换行符可防止 //(单行 cmets)出现问题。

      ⭐ 如果解析器不可用,下一个最佳选择是使用久经考验的技术,例如 Angular.js 的依赖注入器正则表达式。我将Lambder's answer 的函数版本与humbletim's answer 组合在一起,并添加了一个可选的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

      【讨论】:

      • 如果我没记错的话,在您的浏览器版本中,FUNC_ARGS 中的第一个条件将同时适用于箭头和传统功能,因此您不需要第二部分,因此您可以取消对 ARROW 的依赖。
      • 这太棒了!我一直在寻找这样的解决方案,它使用解析器来覆盖 ES6 语法。我打算用它来创建一个开玩笑的“实现接口”匹配器,因为简单地使用 function.length 对默认参数有限制,我希望能够断言其余参数。
      • 值得指出的是,默认值中包含括号的第五个测试用例目前失败了。我希望我的 regex-fu 足够强大,可以修复,抱歉!
      【解决方案8】:

      注意:如果您想在顶级解决方案中使用 ES6 参数解构,请添加以下行。

      if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -1)
      

      【讨论】:

        【解决方案9】:

        我已经修改了取自 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({})

        【讨论】:

          【解决方案10】:

          这是一种方法:

          // Utility function to extract arg name-value pairs
          function getArgs(args) {
              var argsObj = {};
          
              var argList = /\(([^)]*)/.exec(args.callee)[1];
              var argCnt = 0;
              var tokens;
              var argRe = /\s*([^,]+)/g;
          
              while (tokens = argRe.exec(argList)) {
                  argsObj[tokens[1]] = args[argCnt++];
              }
          
              return argsObj;
          }
          
          // Test subject
          function add(number1, number2) {
              var args = getArgs(arguments);
              console.log(args); // ({ number1: 3, number2: 4 })
          }
          
          // Invoke test subject
          add(3, 4);
          

          注意:这只适用于支持arguments.callee的浏览器。

          【讨论】:

          • while 循环使用提供的代码导致无限循环(截至 20171121at1047EDT)
          • @George2.0Hope 感谢您指出这一点。我会更新答案。
          • args.toSource 不是函数(第 20 行)...但即使您将其更改为:console.log(args.toString()); ... 你会得到 ... [object Object] ... 如果你这样做会更好:console.log(JSON.stringify(args));
          • 自 2009 年以来情况发生了很大变化!
          • 万一有人出现在这里使用 React,这个很棒的功能在严格模式下将无法工作。
          【解决方案11】:

          这个包使用 recast 来创建一个 AST,然后从它们收集参数名称,这允许它支持模式匹配、默认参数、箭头函数和其他 ES6 特性。

          https://www.npmjs.com/package/es-arguments

          【讨论】:

            【解决方案12】:

            下面我给你一个简短的例子:

            function test(arg1,arg2){
                var funcStr = test.toString()
                var leftIndex = funcStr.indexOf('(');
                var rightIndex = funcStr.indexOf(')');
                var paramStr = funcStr.substr(leftIndex+1,rightIndex-leftIndex-1);
                var params = paramStr.split(',');
                for(param of params){
                    console.log(param);   // arg1,arg2
                }
            }
            
            test();
            

            【讨论】:

            • 该示例只是为您获取参数名称。
            • 这个答案很有用,但它没有回答问题。我投了赞成票,因为它解决了我的问题,但我认为它应该移到更合适的地方。也许搜索相关问题?
            【解决方案13】:

            我知道这是一个老问题,但是初学者一直在复制粘贴这个问题,好像这是任何代码中的好习惯。大多数情况下,必须解析函数的字符串表示以使用其参数名称只是隐藏了代码逻辑中的缺陷。

            函数的参数实际上存储在一个名为arguments 的类数组对象中,其中第一个参数是arguments[0],第二个是arguments[1],依此类推。在括号中写入参数名称可以看作是一种简写语法。这个:

            function doSomething(foo, bar) {
                console.log("does something");
            }
            

            ...等同于:

            function doSomething() {
                var foo = arguments[0];
                var bar = arguments[1];
            
                console.log("does something");
            }
            

            变量本身存储在函数的作用域中,而不是作为对象中的属性。无法通过代码检索参数名称,因为它只是人类语言中表示变量的符号。

            我一直认为函数的字符串表示是一种用于调试目的的工具,尤其是因为这个arguments 类数组对象。首先,您不需要为参数命名。如果你尝试解析一个字符串化的函数,它实际上并没有告诉你它可能需要额外的未命名参数。

            这是一个更糟糕、更常见的情况。如果一个函数有超过 3 或 4 个参数,则将其传递给一个对象可能是合乎逻辑的,这样更容易使用。

            function saySomething(obj) {
              if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
            }
            
            saySomething({sender: "user123", message: "Hello world"});
            

            在这种情况下,函数本身将能够读取它接收的对象并查找其属性并获取它们的名称和值,但尝试解析函数的字符串表示只会给你“obj”对于参数,这根本没有用。

            【讨论】:

            • 我认为这样的情况通常是:调试/记录,某种做时髦的东西的装饰器(技术术语?),或者为您的应用程序构建一个依赖注入框架以自动注入关于参数名称(这是角度的工作方式)。另一个非常有趣的用例是 promisify-node(它是一个库,它接受一个通常接受回调然后将其转换为 Promise 的函数)。他们使用它来查找回调的通用名称(如 cb/callback/etc),然后他们可以在包装之前检查函数是异步还是同步。
            • this file for their parser。这有点天真,但它可以处理大多数情况。
            • 很有趣,我很惊讶这样的图书馆会受到关注。好吧,像我描述的那样有问题的情况有多个未解决的问题。正如我所说,如果它是用于调试目的,那很好,但是在生产环境中依赖函数的字符串转换太冒险了。
            • 在“有趣”的旁注中,您可以通过运行以下命令使所有函数都像匿名内置函数一样:Function.prototype.toString = function () { return 'function () { [native code] }'; };
            • 同意,这样处理有点乱。最好和最直接的用法是依赖注入。在这种情况下,您拥有代码并且可以处理命名和其他代码区域。我认为这是它最有用的地方。我目前使用esprima(而不是正则表达式)和ES6 Proxyconstructor trapapply trap)和Reflection 来处理我的一些模块的DI。它相当坚固。
            【解决方案14】:

            这里的很多答案都使用正则表达式,这很好,但它不能很好地处理语言的新添加(如箭头函数和类)。另外值得注意的是,如果你在缩小代码上使用这些函数中的任何一个,它就会变得 ?。它将使用任何缩小的名称。 Angular 通过允许您在将参数注册到 DI 容器时传入与参数顺序匹配的有序字符串数组来解决此问题。以此类推:

            var esprima = require('esprima');
            var _ = require('lodash');
            
            const parseFunctionArguments = (func) => {
                // allows us to access properties that may or may not exist without throwing 
                // TypeError: Cannot set property 'x' of undefined
                const maybe = (x) => (x || {});
            
                // handle conversion to string and then to JSON AST
                const functionAsString = func.toString();
                const tree = esprima.parse(functionAsString);
                console.log(JSON.stringify(tree, null, 4))
                // We need to figure out where the main params are. Stupid arrow functions ?
                const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
                const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params 
                                                 : maybe(_.first(tree.body)).params;
            
                // extract out the param names from the JSON AST
                return _.map(params, 'name');
            };
            

            这处理了原始解析问题和更多函数类型(例如箭头函数)。以下是它可以和不能按原样处理的想法:

            // I usually use mocha as the test runner and chai as the assertion library
            describe('Extracts argument names from function signature. ?', () => {
                const test = (func) => {
                    const expectation = ['it', 'parses', 'me'];
                    const result = parseFunctionArguments(toBeParsed);
                    result.should.equal(expectation);
                } 
            
                it('Parses a function declaration.', () => {
                    function toBeParsed(it, parses, me){};
                    test(toBeParsed);
                });
            
                it('Parses a functional expression.', () => {
                    const toBeParsed = function(it, parses, me){};
                    test(toBeParsed);
                });
            
                it('Parses an arrow function', () => {
                    const toBeParsed = (it, parses, me) => {};
                    test(toBeParsed);
                });
            
                // ================= cases not currently handled ========================
            
                // It blows up on this type of messing. TBH if you do this it deserves to 
                // fail ? On a tech note the params are pulled down in the function similar 
                // to how destructuring is handled by the ast.
                it('Parses complex default params', () => {
                    function toBeParsed(it=4*(5/3), parses, me) {}
                    test(toBeParsed);
                });
            
                // This passes back ['_ref'] as the params of the function. The _ref is a 
                // pointer to an VariableDeclarator where the ✨? happens.
                it('Parses object destructuring param definitions.' () => {
                    function toBeParsed ({it, parses, me}){}
                    test(toBeParsed);
                });
            
                it('Parses object destructuring param definitions.' () => {
                    function toBeParsed ([it, parses, me]){}
                    test(toBeParsed);
                });
            
                // Classes while similar from an end result point of view to function
                // declarations are handled completely differently in the JS AST. 
                it('Parses a class constructor when passed through', () => {
                    class ToBeParsed {
                        constructor(it, parses, me) {}
                    }
                    test(ToBeParsed);
                });
            });
            

            取决于你想将它用于 ES6 代理和解构可能是你最好的选择。例如,如果你想将它用于依赖注入(使用参数的名称),那么你可以这样做:

            class GuiceJs {
                constructor() {
                    this.modules = {}
                }
                resolve(name) {
                    return this.getInjector()(this.modules[name]);
                }
                addModule(name, module) {
                    this.modules[name] = module;
                }
                getInjector() {
                    var container = this;
            
                    return (klass) => {
                        console.log(klass);
                        var paramParser = new Proxy({}, {
                            // The `get` handler is invoked whenever a get-call for
                            // `injector.*` is made. We make a call to an external service
                            // to actually hand back in the configured service. The proxy
                            // allows us to bypass parsing the function params using
                            // taditional regex or even the newer parser.
                            get: (target, name) => container.resolve(name),
            
                            // You shouldn't be able to set values on the injector.
                            set: (target, name, value) => {
                                throw new Error(`Don't try to set ${name}! ?`);
                            }
                        })
                        return new klass(paramParser);
                    }
                }
            }
            

            它不是目前最先进的解析器,但如果您想使用 args 解析器进行简单的 DI,它可以让您了解如何使用代理来处理它。然而,这种方法有一个轻微的警告。我们需要使用解构赋值而不是普通参数。当我们传入注入器代理时,解构与在对象上调用 getter 相同。

            class App {
               constructor({tweeter, timeline}) {
                    this.tweeter = tweeter;
                    this.timeline = timeline;
                }
            }
            
            class HttpClient {}
            
            class TwitterApi {
                constructor({client}) {
                    this.client = client;
                }
            }
            
            class Timeline {
                constructor({api}) {
                    this.api = api;
                }
            }
            
            class Tweeter {
                constructor({api}) {
                    this.api = api;
                }
            }
            
            // Ok so now for the business end of the injector!
            const di = new GuiceJs();
            
            di.addModule('client', HttpClient);
            di.addModule('api', TwitterApi);
            di.addModule('tweeter', Tweeter);
            di.addModule('timeline', Timeline);
            di.addModule('app', App);
            
            var app = di.resolve('app');
            console.log(JSON.stringify(app, null, 4));
            

            这会输出以下内容:

            {
                "tweeter": {
                    "api": {
                        "client": {}
                    }
                },
                "timeline": {
                    "api": {
                        "client": {}
                    }
                }
            }
            

            它连接了整个应用程序。最好的一点是该应用程序易于测试(您只需实例化每个类并传入模拟/存根/等)。此外,如果您需要更换实现,您可以从一个地方进行。由于 JS 代理对象,这一切都是可能的。

            注意:在它准备好用于生产之前需要做很多工作,但它确实给出了它的外观。

            答案有点晚,但它可能会帮助其他正在考虑同样事情的人。 ?

            【讨论】:

              【解决方案15】:

              哇,已经有这么多答案了。我很确定这会被埋没。尽管如此,我认为这可能对某些人有用。

              我对选择的答案并不完全满意,因为在 ES6 中它不适用于默认值。而且它也不提供默认值信息。我还想要一个不依赖外部库的轻量级函数。

              此函数对于调试目的非常有用,例如:记录调用函数及其参数、默认参数值和参数。

              我昨天花了一些时间,破解了正确的正则表达式来解决这个问题,这就是我想出的。效果很好,我对结果非常满意:

              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 提取值变得非常复杂。

              如果这对您有用,请告诉我!希望听到一些反馈!

              【讨论】:

                【解决方案16】:
                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;
                }
                

                【讨论】:

                  【解决方案17】:

                  好吧,这是一个老问题,有很多足够的答案。 这是我不使用正则表达式的产品,除了剥离空白的琐碎任务。 (我应该注意,“strips_cmets”函数实际上将它们隔开,而不是物理移除它们。那是因为我在其他地方使用它,并且由于各种原因需要原始非注释标记的位置保持不变)

                  这是一个相当长的代码块,因为此粘贴包含一个迷你测试框架。

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

                  【讨论】:

                    【解决方案18】:

                    这个问题的答案需要 3 个步骤:

                    1. 获取传递给函数的实际参数的值(我们称之为argValues)。这很简单,因为它将在函数内以arguments 的形式提供。
                    2. 从函数签名中获取参数名称(我们称之为argNames)。这并不容易,需要解析函数。无需自己执行复杂的正则表达式并担心边缘情况(默认参数,cmets,...),您可以使用像 babylon 这样的库,它将函数解析为抽象语法树,您可以从中获取参数的名称。
                    3. 最后一步是将 2 个数组合并为 1 个数组,其中包含所有参数的名称和值。

                    代码会是这样的

                    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

                    【讨论】:

                      【解决方案19】:

                      这是一个更新的解决方案,它试图以紧凑的方式解决上述所有边缘情况:

                      function $args(func) {  
                          return (func + '')
                            .replace(/[/][/].*$/mg,'') // strip single-line comments
                            .replace(/\s+/g, '') // strip white space
                            .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
                            .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
                            .replace(/=[^,]+/g, '') // strip any ES6 defaults  
                            .split(',').filter(Boolean); // split & filter [""]
                      }  
                      

                      简短的测试输出(完整的测试用例附在下面):

                      'function (a,b,c)...' // returns ["a","b","c"]
                      'function ()...' // returns []
                      'function named(a, b, c) ...' // returns ["a","b","c"]
                      'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
                      'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
                      'function( a, b = 1, c )...' // returns ["a","b","c"]
                      'function (a=4*(5/3), b) ...' // returns ["a","b"]
                      'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
                      'function (a /* fooled you...' // returns ["a","b"]
                      'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
                      'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
                      'function (a,b)...' // returns ["a","b"]
                      'function $args(func) ...' // returns ["func"]
                      'null...' // returns ["null"]
                      'function Object() ...' // returns []
                      

                      function $args(func) {  
                          return (func + '')
                            .replace(/[/][/].*$/mg,'') // strip single-line comments
                            .replace(/\s+/g, '') // strip white space
                            .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
                            .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
                            .replace(/=[^,]+/g, '') // strip any ES6 defaults  
                            .split(',').filter(Boolean); // split & filter [""]
                      }  
                      
                      // test cases  
                      document.getElementById('console_info').innerHTML = (
                      [  
                        // formatting -- typical  
                        function(a,b,c){},  
                        function(){},  
                        function named(a, b,  c) {  
                      /* multiline body */  
                        },  
                          
                        // default values -- conventional  
                        function(a /* = 1 */, b /* = true */) { a = a||1; b=b||true; },  
                        function fprintf(handle, fmt /*, ...*/) { },  
                        
                        // default values -- ES6  
                        "function( a, b = 1, c ){}",  
                        "function (a=4*(5/3), b) {}",  
                        
                        // embedded comments -- sardonic  
                        function(a, // single-line comment xjunk) {}
                          b //,c,d
                        ) // single-line comment
                        {},  
                        function(a /* fooled you{*/,b){},  
                        function /* are you kidding me? (){} */(a /* function() yes */,  
                         /* no, */b)/* omg! */{/*}}*/},  
                        
                        // formatting -- sardonic  
                        function  (  A,  b  
                      ,c  ,d  
                        )  
                        {  
                        },  
                        
                        // by reference  
                        this.jQuery || function (a,b){return new e.fn.init(a,b,h)},
                        $args,  
                        
                        // inadvertent non-function values  
                        null,  
                        Object  
                      ].map(function(f) {
                          var abbr = (f + '').replace(/\n/g, '\\n').replace(/\s+|[{]+$/g, ' ').split("{", 1)[0] + "...";
                          return "    '" + abbr + "' // returns " + JSON.stringify($args(f));
                        }).join("\n") + "\n"); // output for copy and paste as a markdown snippet
                      &lt;pre id='console_info'&gt;&lt;/pre&gt;

                      【讨论】:

                      • 当存在单行 cmets 时会中断。试试这个:return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
                      • 您应该将func + '' 替换为Function.toString.call(func) 以防止函数具有自定义 .toString() 实现的情况。
                      • 胖箭头 => .split(/\)[\{=]/, 1)[0]
                      • 这会将解构对象(如({ a, b, c }))拆分为解构内部的所有参数。为了保持解构对象完好无损,请将最后一个 .split 更改为:.split(/,(?![^{]*})/g)
                      • 当存在包含“//”或“/*”的默认字符串值时,这也不起作用
                      【解决方案20】:

                      函数参数字符串值图像动态来自 JSON。由于 item.product_image2 是一个 URL 字符串,所以在参数内部调用 changeImage 时需要将其放在引号中。

                      我的功能点击

                      items+='<img src='+item.product_image1+' id="saleDetailDivGetImg">';
                      items+="<img src="+item.product_image2+"  onclick='changeImage(\""+item.product_image2+"\");'>";
                      

                      我的功能

                      <script type="text/javascript">
                      function changeImage(img)
                       {
                          document.getElementById("saleDetailDivGetImg").src=img;
                          alert(img);
                      }
                      </script>
                      

                      【讨论】:

                        【解决方案21】:

                        以下函数将返回传入的任何函数的参数名称数组。

                        var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
                        var ARGUMENT_NAMES = /([^\s,]+)/g;
                        function getParamNames(func) {
                          var fnStr = func.toString().replace(STRIP_COMMENTS, '');
                          var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
                          if(result === null)
                             result = [];
                          return result;
                        }
                        

                        示例用法:

                        getParamNames(getParamNames) // returns ['func']
                        getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
                        getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
                        getParamNames(function (){}) // returns []
                        

                        编辑

                        随着 ES6 的发明,这个函数可以被默认参数触发。这是一个在大多数情况下应该有效的快速技巧:

                        var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;
                        

                        我说大多数情况是因为有些事情会绊倒它

                        function (a=4*(5/3), b) {} // returns ['a']
                        

                        编辑: 我还注意到 vikasde 也需要数组中的参数值。这已经在名为 arguments 的局部变量中提供。

                        摘自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments:

                        参数对象不是数组。它类似于 Array,但除了长度之外没有任何 Array 属性。例如,它没有 pop 方法。但是它可以转换为一个真正的数组:

                        var args = Array.prototype.slice.call(arguments);
                        

                        如果数组泛型可用,则可以使用以下替代:

                        var args = Array.slice(arguments);
                        

                        【讨论】:

                        • 请注意,由于 cmets 和空格,此解决方案可能会失败 - 例如:var fn = function(a /* fooled you)*/,b){}; 将导致 ["a", "/*", "fooled", "you"]
                        • 我修改了函数以在没有任何参数时返回一个空数组(而不是 null)
                        • 编译正则表达式是有成本的,因此您要避免多次编译复杂的正则表达式。这就是为什么它在函数之外完成
                        • 更正:打算用 perl 允许的 /s 修饰符修改正则表达式,所以 '.'也可以匹配换行符。这对于 /* */ 中的多行 cmets 是必需的。原来 Javascript 正则表达式不允许 /s 修饰符。使用 [/s/S] 的原始正则表达式确实匹配换行符。 SOOO,请忽略之前的评论。
                        • 使用 ES6 箭头函数具有像 (a => a*10) 这样的单个参数,它无法提供所需的输出。
                        【解决方案22】:

                        无论解决方案如何,它都不能在奇怪的函数上中断,toString() 看起来很奇怪:

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

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

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

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

                        【讨论】:

                          【解决方案23】:

                          不易出现空格和 cmets 错误的解决方案是:

                          var fn = function(/* whoa) */ hi, you){};
                          
                          fn.toString()
                            .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
                            .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
                            .split(/,/)
                          
                          ["hi", "you"]
                          

                          【讨论】:

                          • @AlexMills 我注意到的一件事是箭头函数规范说它们不应该被视为“函数”。这意味着它不适合与数组函数匹配。 'this' 的设置方式不同,它们也不应该作为函数调用。这是我通过艰难的方式学到的东西。 ($myService) => $myService.doSomething() 看起来很酷,但它是对数组函数的滥用。
                          【解决方案24】:

                          从@jack-allan 获取answer,我稍微修改了函数以允许ES6默认属性,例如:

                          function( a, b = 1, c ){};
                          

                          仍然返回[ 'a', 'b' ]

                          /**
                           * Get the keys of the paramaters of a function.
                           *
                           * @param {function} method  Function to get parameter keys for
                           * @return {array}
                           */
                          var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
                          var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
                          function getFunctionParameters ( func ) {
                              var fnStr = func.toString().replace(STRIP_COMMENTS, '');
                              var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
                              var result = argsList.match( ARGUMENT_NAMES );
                          
                              if(result === null) {
                                  return [];
                              }
                              else {
                                  var stripped = [];
                                  for ( var i = 0; i < result.length; i++  ) {
                                      stripped.push( result[i].replace(/[\s,]/g, '') );
                                  }
                                  return stripped;
                              }
                          }
                          

                          【讨论】:

                          • 谢谢,你是这个线程中唯一对我有用的。
                          【解决方案25】:
                          (function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')
                          

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

                          【讨论】:

                          • 这在很多情况下都不起作用,包括任何带有换行符 (\r\n) 的代码,以及在其函数体内包含 () 字符的任何 func 代码!例如:myFunc(p1, p2) { if(p1&gt;0){} }
                          【解决方案26】:

                          这很容易。

                          首先有一个已弃用的arguments.callee — 对被调用函数的引用。 其次,如果您有对函数的引用,则可以轻松获得它们的文本表示。 第三,如果你调用你的函数作为构造函数,你也可以通过 yourObject.constructor 获得一个链接。 注意:第一个解决方案已弃用,因此如果您不能不使用它,您还必须考虑您的应用架构。 如果您不需要确切的变量名称,只需在函数内部变量 arguments 中使用,无需任何魔法。

                          https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

                          他们都会调用 toString 并用 re 替换,这样我们就可以创建一个助手:

                          // 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:实际上为 Jack Allan 提供了更好的解决方案。 GJ杰克!

                          【讨论】:

                          • 如果您使用SomeFuncName 而不是arguments.callee(两者都指向函数对象本身),这可能会更直接。
                          【解决方案27】:
                          //See this:
                          
                          
                          // global var, naming bB
                          var bB = 5;
                          
                          //  Dependency Injection cokntroller
                          var a = function(str, fn) {
                            //stringify function body
                            var fnStr = fn.toString();
                          
                            // Key: get form args to string
                            var args = fnStr.match(/function\s*\((.*?)\)/);
                            // 
                            console.log(args);
                            // if the form arg is 'bB', then exec it, otherwise, do nothing
                            for (var i = 0; i < args.length; i++) {
                              if(args[i] == 'bB') {
                                fn(bB);
                              }
                            }
                          }
                          // will do nothing
                          a('sdfdfdfs,', function(some){
                          alert(some)
                          });
                          // will alert 5
                          
                          a('sdfdsdsfdfsdfdsf,', function(bB){
                          alert(bB)
                          });
                          
                          // see, this shows you how to get function args in string
                          

                          【讨论】:

                            【解决方案28】:

                            下面是取自 AngularJS 的代码,它使用该技术实现其依赖注入机制。

                            这里是来自http://docs.angularjs.org/tutorial/step_05的解释

                            Angular 的依赖注入器为你的控制器提供服务 在构造控制器时。依赖注入器也 负责创建服务可能的任何传递依赖项 有(服务通常依赖于其他服务)。

                            请注意,参数的名称很重要,因为注入器 使用这些来查找依赖项。

                            /**
                             * @ngdoc overview
                             * @name AUTO
                             * @description
                             *
                             * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
                             */
                            
                            var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
                            var FN_ARG_SPLIT = /,/;
                            var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
                            var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\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);
                                  forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
                                    arg.replace(FN_ARG, function(all, underscore, name){
                                      $inject.push(name);
                                    });
                                  });
                                  fn.$inject = $inject;
                                }
                              } else if (isArray(fn)) {
                                last = fn.length - 1;
                                assertArgFn(fn[last], 'fn')
                                $inject = fn.slice(0, last);
                              } else {
                                assertArgFn(fn, 'fn', true);
                              }
                              return $inject;
                            }
                            

                            【讨论】:

                            • @apaidnerd 显然是恶魔之血和撒旦的产物。正则表达式?!如果在 JS 中有一个内置的方式会很酷,不是吗。
                            • @apaidnerd,太真实了!只是想 - 这到底是如何实施的?实际上我考虑过使用 functionName.toString() 但我希望更优雅(也许更快)
                            • @sasha.sochka,在意识到没有内置方法可以使用 javascript 获取参数名称后,来到这里想知道完全相同的事情
                            • 为了节省时间,您可以通过annotate = angular.injector.$$annotate从角度获取此功能
                            • 我确实在互联网上搜索了这个主题,因为我很好奇 Angular 是如何做到的......现在我知道了,而且我知道的太多了!
                            【解决方案29】:

                            我通常是怎么做的:

                            function name(arg1, arg2){
                                var args = arguments; // array: [arg1, arg2]
                                var objecArgOne = args[0].one;
                            }
                            name({one: "1", two: "2"}, "string");
                            

                            您甚至可以通过函数名称引用参数,例如:

                            name.arguments;
                            

                            希望这会有所帮助!

                            【讨论】:

                            • 函数参数的名称在哪里?
                            • 啊...你的意思是你想要它的散列形式?好像:var args = name.arguments; console.log('I WANNa SEE', args); 输出类似“{arg1: {...}, arg2: 'string'}”?这可能会解决问题:(function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');。函数参数是一个类似“数组”的对象,具有可枚举的索引。我不认为哈希映射是可能的,但是如果你有超过 4 个参数,你可以只传递一个 Object-literal,这是一种很好的做法。
                            【解决方案30】:

                            您可以使用“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]);    
                            

                            【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2015-06-18
                            • 1970-01-01
                            • 2022-10-02
                            • 2019-06-05
                            相关资源
                            最近更新 更多