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

假设我的函数是这样的

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

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


当前回答

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

其他回答

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

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:

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

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

如果Array泛型可用,则可以使用以下方法:

var args = Array.slice(arguments);

我已经阅读了这里的大部分答案,我想加上我的一句话。

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

or

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
){}

还有Bubersson对Jack Allan的回答的评论:

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

__

解释

新RegExp (' (?: + Function.name + \ \年代* | ^)\ \ s *\\((.*?)\\)')

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

如果函数名是"foo" (function foo()), RegExp将是/foo\s*\((.*?)\)/。

Function.toString()。替换(\ n / g,”)

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

.exec(…)[1]

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

.replace (/ \ / \ * . * ?\ \ * / / g,”)。Replace (/ /g, ")

这将删除/*和*/内的所有注释,并删除所有空格。


这现在也支持读取和理解箭头(=>)函数,如f = (a, b) => void 0;,其中function . tostring()将返回(a, b) => void 0,而不是普通函数的函数f(a, b){返回void 0;}。原来的正则表达式会在混乱中抛出一个错误,但现在已经解决了这个问题。

的变化是新的正则表达式(Function.name + \ \年代 *\\((.*?)\\)') (/ 函数\ s *\((.*?)\)/) 新的正则表达式(’(?:+ Function.name + \ \年代 *|^)\\((.*?)\\)') (/(?: 函数\ s *|^)\((.*?)\)/)


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

下面是来自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;
}

我以前试过这样做,但从来没有找到一个实用的方法来完成它。我最终传递了一个对象,然后循环遍历它。

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

空格和注释不容易出错的解决方案是:

var fn = function(/* whoa) */ hi, you){};

fn.toString()
  .replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
  .match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
  .split(/,/)

["hi", "you"]