如何动态获取函数参数名称/值?

时间:2009-06-17 15:57:14

标签: javascript reflection function-parameter

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

假设我的函数看起来像这样:

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

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

33 个答案:

答案 0 :(得分:301)

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

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,但除了length之外没有任何Array属性。例如,它没有pop方法。但是它可以转换为真正的数组:

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

如果Array泛型可用,则可以使用以下代码:

var args = Array.slice(arguments);

答案 1 :(得分:118)

以下是从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;
}

答案 2 :(得分:34)

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

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
<pre id='console_info'></pre>

答案 3 :(得分:21)

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

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

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

["hi", "you"]

答案 4 :(得分:16)

这里的很多答案都使用了正则表达式,这很好但是它并没有很好地处理语言的新增功能(比如箭头函数和类)。另外值得注意的是,如果你在缩小代码上使用这些功能中的任何一个,那么它就会消失。它将使用任何缩小的名称。 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 Proxies和解构可能是您最好的选择。例如,如果您想将它用于依赖注入(使用参数的名称),那么您可以按如下方式执行:

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,它会让您了解如何使用Proxy来处理它。然而,这种方法有一点需要注意。我们需要使用解构赋值而不是普通参数。当我们传入注入器代理时,解构与调用对象上的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代理对象,这一切都是可能的。

注意:在准备好投入生产之前,需要做很多工作,但它确实会让人知道它会是什么样子。

答案有点晚,但它可能有助于其他想到同样事情的人。

答案 5 :(得分:12)

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

函数的参数实际存储在名为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”对于参数,这根本没用。

答案 6 :(得分:8)

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

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

⭐如果解析器不可用,那么下一个最佳选择是使用像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

答案 7 :(得分:7)

(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')

=&GT; [“a”,“b”,“c”]

答案 8 :(得分:6)

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

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

答案 9 :(得分:6)

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

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

答案 10 :(得分:5)

我在这里已经阅读了大部分答案,我想加上我的一行代码。

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*\\((.*?)\\)').exec(func.toString().replace(/\n/g, ''))[1].replace(/\/\*.*?\*\//g, '').replace(/ /g, '');
}

或ECMA6中的单线函数

var getParameters = func => new RegExp(func.name+'\\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's answer的评论:

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

__

解释

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

这会使new RegExp(Function.name+'\\s*\\((.*?)\\)')创建Regular Exponent。我必须使用new RegExp,因为我正在将一个变量(Function.name(被定位函数的名称)注入RegExp。

示例如果函数名称为“foo”(function foo()),则RegExp将为/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, '')

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

如果要将所有参数放入数组而不是用逗号分隔的字符串,最后只需添加.split(',')

答案 11 :(得分:4)

我不知道这个解决方案是否适合您的问题,但它可以让您重新定义您想要的任何功能,而无需更改使用它的代码。现有的调用将使用定位的params,而函数实现可以使用“named params”(单个散列参数)。

我认为你无论如何都会修改现有的函数定义,为什么不能拥有一个能满足你想要的工厂函数呢?

<!DOCTYPE html>

<html>
<head>
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
var withNamedParams = function(params, lambda) {
    return function() {
        var named = {};
        var max   = arguments.length;

        for (var i=0; i<max; i++) {
            named[params[i]] = arguments[i];
        }

        return lambda(named);
    };
};

var foo = withNamedParams(["a", "b", "c"], function(params) {
    for (var param in params) {
        alert(param + ": " + params[param]);
    }
});

foo(1, 2, 3);
</script>
</head>
<body>

</body>
</html>

希望它有所帮助。

答案 12 :(得分:2)

我不知道如何获取参数列表,但你可以这样做以获得它所期望的数量。

alert(doSomething.length);

答案 13 :(得分:2)

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

答案 14 :(得分:1)

//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

答案 15 :(得分:1)

由于尚未提及,因此如果您使用的是Typescript,则可以在使用Decorators时发出元数据,这将允许您获取参数名称和类型。

仅当类/函数/属性上带有装饰器时,才会发射元数据。
不管哪个装饰器。

可以通过在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();
const funcType = Reflect.getMetadata('design:type', instance.sayHello);
const funcParams = Reflect.getMetadata('design:paramtypes', instance.sayHello);

例如,在较新版本的Angular中,它用于确定要注入的内容-> https://stackoverflow.com/a/53041387/1087372

答案 16 :(得分:1)

执行此操作的正确方法是使用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()来实现。

答案 17 :(得分:1)

我修改了从 AngularJS 中获取的版本,该版本实现了一个依赖注入机制,可以在没有Angular的情况下工作。我还更新了STRIP_COMMENTS正则表达式以使用ECMA6,因此它支持签名中的默认值等内容。

&#13;
&#13;
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({})
&#13;
&#13;
&#13;

答案 18 :(得分:1)

此软件包使用recast来创建AST,然后从它们收集参数名称,这允许它支持模式匹配,默认参数,箭头函数和其他ES6功能。

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

答案 19 :(得分:1)

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

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

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

我昨天花了一些时间来解决这个问题,这就是我提出的问题。它运作良好,我对结果非常满意:

&#13;
&#13;
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\'' ] ]
&#13;
&#13;
&#13;

正如您所知,一些参数名称消失,因为Babel转换器将它们从函数中删除。如果你在最新的NodeJS中运行它,它按预期工作(评论的结果来自NodeJS)。

另一个注释,如评论中所述,它不适用于内联箭头函数作为默认值。这简直使得使用RegExp提取值非常复杂。

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

答案 20 :(得分:1)

答案需要3个步骤:

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

    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

答案 21 :(得分:1)

我通常如何做到:

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

您甚至可以通过以下函数名称来引用args:

name.arguments;

希望这有帮助!

答案 22 :(得分:1)

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

答案 23 :(得分:0)

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

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

sudo apt-get install libglu1-mesa-dev:i386
...
The following extra packages will be installed:
  libcgmanager0:i386 libdbus-1-3:i386 libdrm-amdgpu1:i386 libdrm-dev:i386
  libgl1-mesa-dev:i386 libgl1-mesa-glx:i386 ...
The following packages will be REMOVED:
  freeglut3-dev libgl1-mesa-dev libglu1-mesa-dev mesa-common-dev
...
0 upgraded, 39 newly installed, 4 to remove and 0 not upgraded.
Need to get 1,595 kB of archives.
After this operation, 5,559 kB of additional disk space will be used

答案 24 :(得分:0)

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

答案 25 :(得分:0)

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

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

答案 26 :(得分:0)

这是一种方式:

// 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的浏览器。

答案 27 :(得分:0)

无论解决方案是什么,它都不能打破那些toString()看起来很奇怪的奇怪功能:

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

screenshot from console

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

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

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

答案 28 :(得分:0)

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

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

答案 29 :(得分:0)

这很容易。

首先有一个已弃用的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:杰克艾伦实际上提供了一个更好的解决方案。 GJ杰克!

答案 30 :(得分:0)

我希望提出支持箭头功能的解决方案,例如 我将本文用于基本正则表达式和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;
  });
}

答案 31 :(得分:-1)

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

我的功能Onclick

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>

答案 32 :(得分:-4)

手动尝试:

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