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


300

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

假设我的函数如下所示:

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

现在,如何从函数内部将参数名称及其值的列表放入数组中?


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

8
让我们将其标记为已回答的好友。没有新的答案来。
马修·格雷夫斯

function doSomething(...args) { /*use args*/}
caub

Answers:


322

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

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/zh-CN/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments

arguments对象不是数组。它类似于Array,但是除length外没有任何Array属性。例如,它没有pop方法。但是可以将其转换为实际的数组:

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

如果可以使用Array泛型,则可以改用以下内容:

var args = Array.slice(arguments);

12
请注意,此解决方案可能由于注释和空格而失败-例如:var fn = function(a /* fooled you)*/,b){};将导致 ["a", "/*", "fooled", "you"]
bubersson

1
我修改了该函数以在没有任何参数时返回一个空数组(而不是null)
BT

2
编译正则表达式是有代价的,因此您要避免多次编译复杂的正则表达式。这就是为什么要在功能之外进行的原因
杰克·艾伦

2
更正:将要使用/ s修饰符来修改正则表达式,而perl则允许使用'。'。也可以匹配换行符。对于/ * * /中的多行注释,这是必需的。事实证明Javascript正则表达式不允许使用/ s修饰符。使用[/ s / S]的原始正则表达式与换行符匹配。SOOO,请忽略先前的评论。
tgoneil 2014年

1
@andes注意,您的测试中包括正则表达式编译。这些只能执行一次。如果将正则表达式编译移至设置步骤而不是测试,则结果可能会有所不同
Jack Allan 2015年

123

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

40
显然,@ apaidnerd充满了恶魔之血和撒旦的产卵。正则表达式?!如果JS中有内置方法,那会很酷,不是吗。
Aditya MP

6
@apaidnerd,非常正确!只是想想-这到底是怎么实现的?其实我曾考虑过使用functionName.toString(),但我希望有一些更优雅(也许更快)的东西
sasha.sochka,2013年

2
@ sasha.sochka,在意识到没有内置的方法来使用javascript获取参数名称后来到这里,想知道一模一样的东西
哈特·

14
为了节省人们的时间,您可以通过角度获取此功能 annotate = angular.injector.$$annotate
尼克

3
我真的在互联网上搜索这个话题,因为我很好奇Angular是如何做到的……现在我知道了,我也知道太多了!
史蒂文·亨特

40

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

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


当存在单行注释时,这会中断。试试这个: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham

4
你或许应该替换func + ''Function.toString.call(func)抵御当函数有一个自定义的ToString()实现的情况。
Paul Go

1
粗箭头=>.split(/\)[\{=]/, 1)[0]
马特

1
它将分解后的对象(如({ a, b, c }))分解为分解内的所有参数。为了使变形后的物体完好无损,请将最后一个更改.split为: .split(/,(?![^{]*})/g)
Michael Auderer

1
当存在包含“ //”或“ / *”的默认字符串值时,这也将不起作用
skerit

23

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

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

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

["hi", "you"]

1
@AlexMills我注意到的一件事是“箭头功能”规范说不应将它们视为“功能”。意味着这不适合与数组函数匹配。“ this”的设置方式不同,它们也不应该作为函数调用。这是我很难学的东西。($ myService)=> $ myService.doSomething()看起来很酷,但这是对数组函数的滥用。
Andrew T Finnell

20

这里很多答案都使用正则表达式,这很好,但是它不能很好地处理语言的新添加内容(例如箭头函数和类)。还要注意的是,如果您在缩小的代码上使用这些功能中的任何一个,它将to。它将使用最小的名称。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 Proxy对象,所有这些都是可能的。

注意:在准备投入生产使用之前,需要做很多工作,但是确实可以使您了解它的外观。

答案有点晚了,但可能会帮助想到同一件事的其他人。👍


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”作为参数,这根本没有用。


1
我认为通常是这样的情况:调试/日志记录,某种做一些时髦的事情的装饰器(技术术语😁),或为应用程序构建依赖项注入框架,以便根据参数名称自动注入(这就是角度作品)。另一个非常有趣的用例是在promisify-node中(这是一个使用通常会执行回调并将其转换为Promise的函数的库)。他们使用它来查找回调的通用名称(例如cb / callback / etc),然后他们可以在包装函数之前检查该函数是否异步或同步。
James Drew

有关其解析器,请参见此文件。这有点天真,但可以处理大多数情况。
James Drew

有趣的是,我很惊讶这样的图书馆受到关注。好吧,关于问题情况(例如我所描述的问题)存在多个未解决的问题。就像我说的那样,如果它用于调试目的就可以了,但是在生产环境中依赖于函数的字符串转换会带来很大的风险。
Domino

在“有趣的”旁注中,可以通过运行以下命令使所有功能像匿名内置程序一样运行:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino

1
同意,像这样处理它有点混乱。最好和最直接的用法是依赖注入。在这种情况下,您拥有代码,并且可以处理命名和其他代码区域。我认为这是最有用的地方。我目前使用esprima(而不是regex)和ES6 Proxy构造函数trapapply trap)并Reflection为我的某些模块处理DI。相当坚固。
James Drew

10

由于JavaScript是一种脚本语言,因此我认为它的自省应该支持获取函数参数名称。破坏该功能违反了首要原则,因此我决定进一步探讨该问题。

这使我想到了这个问题,但没有内置解决方案。这导致我得到这个答案该答案解释说该功能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的答案James Drew的答案是最好的方法。

我尝试了巴比伦esprima解析器,但不幸的是,正如Mateusz Charytoniuk的回答所指出的那样,它们无法解析独立的匿名函数。我通过将代码括在括号中,从而找到了另一种解决方法,以免更改逻辑:

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

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

⭐如果没有可用的解析器,则下一个最佳选择是使用可靠的技术,例如Angular.js的依赖项注入器正则表达式。我将Lambder答案的功能版本与Humbletim答案结合在一起,并添加了一个可选的ARROW布尔值,用于控制正则表达式是否允许ES6 箭头功能。


这是我汇总的两个解决方案。请注意,它们没有逻辑来检测函数是否具有有效的语法,它们仅提取参数。通常没关系,因为我们通常将解析函数传递给,getArguments()因此它们的语法已经有效。

我将尽最大努力来策划这些解决方案,但是如果没有JavaScript维护者的努力,这将仍然是一个开放的问题。

Node.js版本(在StackOverflow支持Node.js之前无法运行):

const parserName = 'babylon';
// const parserName = 'esprima';
const parser = require(parserName);

function getArguments(func) {
    const maybe = function (x) {
        return x || {}; // optionals support
    }

    try {
        const ast = parser.parse("(\n" + func.toString() + "\n)");
        const program = parserName == 'babylon' ? ast.program : ast;

        return program
            .body[0]
            .expression
            .params
            .map(function(node) {
                return node.name || maybe(node.left).name || '...' + maybe(node.argument).name;
            });
    } catch (e) {
        return []; // could also return null
    }
};

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

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

console.log('');
console.log('////////// STRINGS //////////');

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

完整的工作示例:

https://repl.it/repls/SandybrownPhonyAngles

浏览器版本(请注意,它以第一个复杂的默认值停止):

function getArguments(func) {
    const ARROW = true;
    const FUNC_ARGS = ARROW ? /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m : /^(function)\s*[^\(]*\(\s*([^\)]*)\)/m;
    const FUNC_ARG_SPLIT = /,/;
    const FUNC_ARG = /^\s*(_?)(.+?)\1\s*$/;
    const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    return ((func || '').toString().replace(STRIP_COMMENTS, '').match(FUNC_ARGS) || ['', '', ''])[2]
        .split(FUNC_ARG_SPLIT)
        .map(function(arg) {
            return arg.replace(FUNC_ARG, function(all, underscore, name) {
                return name.split('=')[0].trim();
            });
        })
        .filter(String);
}

////////// TESTS //////////

function logArgs(func) {
	let object = {};

	object[func] = getArguments(func);

	console.log(object);
// 	console.log(/*JSON.stringify(*/getArguments(func)/*)*/);
}

console.log('');
console.log('////////// MISC //////////');

logArgs((a, b) => {});
logArgs((a, b = 1) => {});
logArgs((a, b, ...args) => {});
logArgs(function(a, b, ...args) {});
logArgs(function(a, b = 1, c = 4 * (5 / 3), d = 2) {});
logArgs(async function(a, b, ...args) {});
logArgs(function async(a, b, ...args) {});

console.log('');
console.log('////////// FUNCTIONS //////////');

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

console.log('');
console.log('////////// STRINGS //////////');

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

完整的工作示例:

https://repl.it/repls/StupendousShowyOffices


如果我没记错的话,在您的浏览器版本中,FUNC_ARGS中的第一个条件将同时适用于箭头功能和传统功能,因此您不需要第二部分,因此可以消除对ARROW的依赖。
pwilcox

这很棒!我一直在寻找一种使用解析器来覆盖ES6语法的解决方案。我计划使用它来创建一个开玩笑的“实现接口”匹配器,因为仅使用function.length就具有默认参数的限制,并且我希望能够断言其余参数。
cue8chalk

值得指出的是,在默认值中包含括号的第五个测试用例当前失败。我希望我的正则表达式功能强大到可以修复,对不起!
Dale Anderson

8

我已经在这里阅读了大多数答案,并且我想添加我的单行代码。

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

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

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

__

说明

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

这就产生了一个正则表达式new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)')。我必须使用,new RegExp因为我正在向Function.nameRegExp中注入变量(,即目标函数的名称)。

示例如果函数名称为“ foo”(function foo()),则RegExp将为/foo\s*\((.*?)\)/

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

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

.exec(...)[1]

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

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

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


现在,这也支持阅读和理解arrow(=>)函数,例如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*|^)\((.*?)\)/


如果要将所有参数放入一个Array中,而不是用逗号分隔的String中,请最后添加.split(',')


相当不错。一行,处理箭头功能,没有外部依赖关系,并且涵盖了足够多的边缘情况,至少用于我的预期用途。如果您可以对函数签名做出一些合理的假设,那么您所需要的就是这些。谢谢!
查理·罗伯茨

不适用于此简单的箭头功能:f = (a, b) => void 0; 在getParameters(f)我得到TypeError: Cannot read property '1' of null
AT

@AT我刚刚更新了答案,以修复对您的问题的支持
Jaketr00 '19

谢谢...但是请记住,括号中不再需要的,所以你可以做这样的事情getParameters(a => b => c => d => a*b*c*d),这与你的代码仍然给出了TypeError: Cannot read property '1' of null......而这一次的作品stackoverflow.com/a/29123804
AT

当函数具有默认值(角色,名称=“ bob”)时不起作用。提取的参数为name =“ bob”,而不是预期的“ name”
Dmitri

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

=> [“ a”,“ b”,“ c”]


7

您也可以使用“ 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"]

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

我必须要使用标准参数而不是单个对象来调用许多已经预定义的函数。更改所有内容将花费大量时间。
vikasde

2
该回答不是对精确定义的原始问题的答案。它显示了完全不同的问题的解决方案。最初的问题涉及AngularJS对其依赖项注入使用的技术。参数名称很有意义,因为它们对应于DI自动提供的模块的依赖关系。
Lambder

4

我不知道此解决方案是否适合您的问题,但是它可以让您重新定义所需的任何功能,而不必更改使用该功能的代码。现有调用将使用定位的参数,而函数实现可能使用“命名参数”(单个哈希参数)。

我以为您无论如何都会修改现有的函数定义,所以为什么不拥有能够满足您需求的工厂函数:

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

希望能帮助到你。


4

正确的方法是使用JS解析器。这是一个使用橡子的例子。

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


值呢?
eran otzap,

2

我不知道如何获取参数列表,但是您可以执行此操作以获取期望的参数数量。

alert(doSomething.length);

function gotcha (a, b = false, c) {}; alert(gotcha.length)
balupton

2

从@ jack-allan那里得到答案,我对功能进行了一些修改,以允许使用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;
    }
}

1
谢谢,您是本主题中唯一对我有用的人。
AT

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;

希望这可以帮助!


4
函数参数的名称在哪里?
Andrej

啊...您是说要散列形式?好像: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...?');。函数参数是一个类似于“数组”的对象,具有可枚举的索引。我认为不可能进行散列映射,但是如果您有四个以上的参数,则可以通过Object-literal,这是一种很好的做法。
科迪2013年

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

1

答案需要3个步骤:

  1. 为了获取传递给函数的实际参数的值(我们称之为argValues)。这很简单,因为它将arguments在函数内部提供。
  2. 要从函数签名中获取参数名称(我们称之为argNames)。这不是那么容易,需要解析函数。您可以自己使用复杂的正则表达式而不用担心边缘情况(默认参数,注释等),而可以使用像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


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

1

哇,已经有这么多答案了。我很确定这被掩埋了。即使如此,我还是认为这对某些人可能有用。

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

该函数对于调试目的非常有用,例如:使用其参数,默认参数值和参数记录称为函数的函数。

我昨天花了一些时间,破解了正确的RegExp来解决这个问题,这就是我想出的。它工作得很好,我对结果感到非常满意:

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

请告诉我这是否对您有用!希望听到一些反馈!


1

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

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

该示例只是为您获取参数名称。
myzhou

该答案很有用,但不能回答问题。我投票赞成,因为它解决了我的问题,但我认为应该将其移至更合适的位置。也许搜索相关问题?
chharvey '17


1

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


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

是否不赞成使用参数?参见上文Ionut G. Stan的建议。
vikasde

vikasde是正确的。不建议访问arguments函数实例的属性。见developer.mozilla.org/en/Core_JavaScript_1.5_Reference/...
约努茨G.斯坦

0

很简单

首先,不推荐使用arguments.callee-对被调用函数的引用。第二,如果您引用了函数,则可以轻松获得它们的文本表示形式。第三,如果您将函数作为构造函数调用,则还可以通过yourObject.constructor进行链接。注意:不建议使用第一个解决方案,因此,如果您不能不使用它,则还必须考虑您的应用程序体系结构。如果您不需要确切的变量名,则只需在函数内部变量内使用arguments即可,无需任何魔术。

https://developer.mozilla.org/zh-CN/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提供了更好的解决方案。杰克杰克!


如果您使用SomeFuncName而不是arguments.callee(都指向函数对象本身),则可能会更加直接。
拉斐尔·史威克

0

不管采用哪种解决方案,它都不能在toString()看起来奇怪的奇怪函数上中断:

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

控制台截图

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

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

每个函数都可以在任何地方使用,唯一的正则表达式是空格删除,由于这种.split技巧,它甚至不能处理整个字符串。


0

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

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

    function do_tests(func) {

    if (typeof func !== 'function') return true;
    switch (typeof func.tests) {
        case 'undefined' : return true;
        case 'object'    : 
            for (var k in func.tests) {

                var test = func.tests[k];
                if (typeof test==='function') {
                    var result = test(func);
                    if (result===false) {
                        console.log(test.name,'for',func.name,'failed');
                        return false;
                    }
                }

            }
            return true;
        case 'function'  : 
            return func.tests(func);
    }
    return true;
} 
function strip_comments(src) {

    var spaces=(s)=>{
        switch (s) {
            case 0 : return '';
            case 1 : return ' ';
            case 2 : return '  ';
        default : 
            return Array(s+1).join(' ');
        }
    };

    var c1 = src.indexOf ('/*'),
        c2 = src.indexOf ('//'),
        eol;

    var out = "";

    var killc2 = () => {
                out += src.substr(0,c2);
                eol =  src.indexOf('\n',c2);
                if (eol>=0) {
                    src = spaces(eol-c2)+'\n'+src.substr(eol+1);
                } else {
                    src = spaces(src.length-c2);
                    return true;
                }

             return false;
         };

    while ((c1>=0) || (c2>=0)) {
         if (c1>=0) {
             // c1 is a hit
             if ( (c1<c2) || (c2<0) )  {
                 // and it beats c2
                 out += src.substr(0,c1);
                 eol = src.indexOf('*/',c1+2);
                 if (eol>=0) {
                      src = spaces((eol-c1)+2)+src.substr(eol+2);
                 } else {
                      src = spaces(src.length-c1);
                      break;
                 }
             } else {

                 if (c2 >=0) {
                     // c2 is a hit and it beats c1
                     if (killc2()) break;
                 }
             }
         } else {
             if (c2>=0) {
                // c2 is a hit, c1 is a miss.
                if (killc2()) break;  
             } else {
                 // both c1 & c2 are a miss
                 break;
             }
         }

         c1 = src.indexOf ('/*');
         c2 = src.indexOf ('//');   
        }

    return out + src;
}

function function_args(fn) {
    var src = strip_comments(fn.toString());
    var names=src.split(')')[0].replace(/\s/g,'').split('(')[1].split(',');
    return names;
}

function_args.tests = [

     function test1 () {

            function/*al programmers will sometimes*/strip_comments_tester/* because some comments are annoying*/(
            /*see this---(((*/ src//)) it's an annoying comment does not help anyone understand if the 
            ,code,//really does
            /**/sucks ,much /*?*/)/*who would put "comment\" about a function like (this) { comment } here?*/{

            }


        var data = function_args(strip_comments_tester);

        return ( (data.length==4) &&
                 (data[0]=='src') &&
                 (data[1]=='code') &&
                 (data[2]=='sucks') &&
                 (data[3]=='much')  );

    }

];
do_tests(function_args);

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


2
while循环使用提供的代码导致无限循环(截至20171121at1047EDT)
George 2.0 Hope

@ George2.0Hope感谢您指出这一点。我将更新答案。
Ates Goral

args.toSource不是函数(第20行)...但是即使将其更改为:console.log(args.toString()); ...您得到... [object Object] ...如果您这样做更好:console.log(JSON.stringify(args));
乔治2.0希望,

自2009年以来,情况发生了很大变化!
Ates Goral

1
万一有人在这里为React出现,这个很棒的功能将无法在严格模式下使用。
个人

0

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

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

-1

从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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.