Как получить имена / значения параметров функции динамически?

Есть ли способ получить имена параметров функции динамически?

Допустим, моя функция выглядит так:

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

Теперь, как мне получить список имен параметров и их значений в массиве внутри функции?

35 ответов

Следующая функция вернет массив имен параметров любой переданной функции.

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:

Объект аргументов не является массивом. Он похож на массив, но не имеет никаких свойств массива, кроме длины. Например, у него нет метода pop. Однако он может быть преобразован в настоящий массив:

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

Если доступны универсальные массивы, вместо них можно использовать следующее:

var args = Array.slice(arguments);

Ниже приведен код, взятый из 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;
}

Вот обновленное решение, которое пытается компактно решить все крайние случаи, упомянутые выше:

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>

Решение, которое менее подвержено ошибкам и пробелам:

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

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

["hi", "you"]

Многие ответы здесь используют регулярные выражения, это хорошо, но он не слишком хорошо обрабатывает новые дополнения к языку (например, функции стрелок и классы). Также следует отметить, что если вы используете какую-либо из этих функций в минимизированном коде, это будет происходить. Он будет использовать любое минимизированное имя. 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);
        }
    }
}

Это не самый продвинутый распознаватель, но он дает представление о том, как вы можете использовать прокси для его обработки, если вы хотите использовать анализатор аргументов для простого DI. Однако в этом подходе есть одна небольшая оговорка. Нам нужно использовать деструктурирующие назначения вместо обычных параметров. Когда мы передаем прокси инжектора, деструктурирование аналогично вызову геттера объекта.

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": {}
        }
    }
}

Его подключили все приложение. Самое приятное, что приложение легко тестировать (вы можете просто создать экземпляр каждого класса и передать его в mocks / stubs / etc). Также, если вам нужно поменять местами реализации, вы можете сделать это из одного места. Все это возможно благодаря объектам JS Proxy.

Примечание: для этого нужно проделать большую работу, прежде чем он будет готов к использованию, но он дает представление о том, как он будет выглядеть.

Это немного поздно в ответе, но это может помочь другим, кто думает о том же.

Я знаю, что это старый вопрос, но новички копировали его так, как будто это было хорошей практикой в ​​любом коде. В большинстве случаев необходимость разбора строкового представления функции для использования имен ее параметров просто скрывает изъян в логике кода.

Параметры функции на самом деле хранятся в похожем на массив объекте 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" для параметров, что совсем не полезно.

Я прочитал большинство ответов здесь, и я хотел бы добавить свою однострочную.

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"

Этот код также будет работать с настройкой функции, которую дал Камило Мартин:

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

Также с комментарием Буберссона к ответу Джека Аллана:

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

__

объяснение

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

Это создает регулярный экспонент с new RegExp(Function.name+'\\s*\\((.*?)\\)'), Я должен использовать new RegExp потому что я вводить переменную (Function.name имя целевой функции) в RegExp.

Пример Если имя функции "foo" (function foo()), RegExp будет /foo\s*\((.*?)\)/,

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

Затем он преобразует всю функцию в строку и удаляет все символы новой строки. Удаление новых строк помогает в настройке функции, которую дал Камило Мартин.

.exec(...)[1]

Это RegExp.prototype.exec функция. Это в основном соответствует обычному показателю (new RegExp()) в строку (Function.toString()). Тогда [1] вернет первую группу захвата, найденную в обычном экспоненте ((.*?)).

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

Это удалит каждый комментарий внутри /* а также */ и удалите все пробелы.


Если вы хотите превратить все параметры в массив вместо строки, разделенной запятыми, в конце просто добавьте .split(','),

Поскольку 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

Время закатать рукава и приступить к работе:

Function Для получения параметров функции требуется синтаксический анализатор, потому что сложные выражения, такие как 4*(5/3) может использоваться в качестве значений по умолчанию. Таким образом , ответ Гаафара или ответ Джеймса Дрю являются наилучшими подходами.

Я попробовал парсеры babylon и esprima, но, к сожалению, они не могут разобрать автономные анонимные функции, как указано в ответе Матеуша Чаритонюка. Я нашел другой обходной путь, заключив код в круглые скобки, чтобы не менять логику:

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

Новые строки предотвращают проблемы с // (однострочные комментарии).

⭐ Если синтаксический анализатор недоступен, следующий лучший вариант - использовать проверенный метод, такой как регулярные выражения инжектора зависимостей Angular.js. Я объединил функциональную версию ответа Ламбдера с ответом Хамблетима и добавил дополнительный ARROW логический для контроля, разрешены ли функции жирной стрелки ES6 регулярными выражениями.


Вот два решения, которые я собрал. Обратите внимание, что они не имеют логики для определения, имеет ли функция допустимый синтаксис, они только извлекают аргументы. Обычно это нормально, так как мы обычно передаем проанализированные функции getArguments() поэтому их синтаксис уже действителен.

Я постараюсь курировать эти решения как можно лучше, но без усилий со стороны сопровождающих JavaScript это останется открытой проблемой.

Версия Node.js (не запускается, пока Stackru не поддерживает 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

Вы также можете использовать парсер 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"]

Правильный способ сделать это - использовать анализатор 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(),

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

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

Я пытался сделать это раньше, но никогда не находил практичный способ сделать это. В итоге я вместо этого передал объект, а затем перебрал его.

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

Я не знаю, подходит ли это решение к вашей проблеме, но оно позволяет вам переопределить любую функцию, которую вы хотите, без необходимости изменять код, который ее использует. Существующие вызовы будут использовать позиционированные параметры, в то время как реализация функции может использовать "именованные параметры" (один хэш-параметр).

Я думал, что вы в любом случае измените существующие определения функций так, почему бы не иметь заводскую функцию, которая делает именно то, что вы хотите:

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

Надеюсь, поможет.

Получив ответ от @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;
    }
}

Я не знаю, как получить список параметров, но вы можете сделать это, чтобы получить, сколько он ожидает.

alert(doSomething.length);

Поскольку об этом еще не упоминалось, если вы используете Typescript, вы можете генерировать метаданные при использовании декораторов, которые позволят вам получить имена и типы параметров.

Метаданные будут отправлены только в том случае, если у класса / функции / опоры есть декоратор.
Неважно, какой декоратор.

Эту функцию можно включить, установив для emitDecoratorMetadata значение true внутри tsconfig.json.

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

Поскольку метаданные еще в начале предложения отражают-метаданные должны быть установлены пакет или не будет определяться 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 это используется для определения того, что вводить -> qaru

Вот мое решение — оно работает для именованных и неименованных функций, асинхронных и неасинхронных функций, асинхронных и неасинхронных лямбда-выражений, а также лямбда-выражений со скобками и без них.

      const STRIP_COMMENTS = /((\/\/.*$)|(\/\*.*\*\/))/mg;
const STRIP_KEYWORDS = /(\s*async\s*|\s*function\s*)+/;
const ARGUMENT_NAMES = /\(([^)]+)\)\s*=>|([a-zA-Z_$]+)\s*=>|[a-zA-Z_$]+\(([^)]+)\)|\(([^)]+)\)/;
const ARGUMENT_SPLIT = /[ ,\n\r\t]+/;
function getParamNames(func) {
    const fnStr = func.toString()
        .replace(STRIP_COMMENTS, "")
        .replace(STRIP_KEYWORDS, "")
        .trim();
    const matches = ARGUMENT_NAMES.exec(fnStr);
    var match;
    if (matches) {
        for (var i = 1; i < matches.length; i++) {
            if (matches[i]) {
                match = matches[i];
                break;
            } 
        }
    }
    if (match === undefined) {
        return [];
    }
    return match.split(ARGUMENT_SPLIT).filter(part => part !== "");
}
//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

Ух ты так много ответов... Я почти уверен, что это похоронят. Тем не менее, я подумал, что это может быть полезно для некоторых.

Я не был полностью удовлетворен выбранными ответами, так как в 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.

Пожалуйста, дайте мне знать, если это было полезно для вас! Хотелось бы услышать некоторые отзывы!

Ответ на это требует 3 шага:

  1. Чтобы получить значения фактических параметров, переданных в функцию (давайте назовем ее argValues). Это просто, так как будет доступно как arguments внутри функции.
  2. Чтобы получить имена параметров из сигнатуры функции (давайте назовем ее argNames). Это не так просто и требует анализа функции. Вместо того, чтобы выполнять сложное регулярное выражение самостоятельно и беспокоиться о крайних случаях (параметры по умолчанию, комментарии, ...), вы можете использовать библиотеку, подобную babylon, которая будет анализировать функцию в абстрактном синтаксическом дереве, из которого можно получить имена параметров.
  3. Последний шаг - объединить 2 массива в один массив, который имеет имя и значение всех параметров.

Код будет таким

const babylon = require("babylon")
function doSomething(a, b, c) {
    // get the values of passed argumenst
    const argValues = arguments

    // get the names of the arguments by parsing the function
    const ast = babylon.parse(doSomething.toString())
    const argNames =  ast.program.body[0].params.map(node => node.name)

    // join the 2 arrays, by looping over the longest of 2 arrays
    const maxLen = Math.max(argNames.length, argValues.length)
    const args = []
    for (i = 0; i < maxLen; i++) { 
       args.push({name: argNames[i], value: argValues[i]})
    }
    console.log(args)

    // implement the actual function here
}

doSomething(1, 2, 3, 4)

и зарегистрированный объект будет

[
  {
    "name": "a",
    "value": 1
  },
  {
    "name": "c",
    "value": 3
  },
  {
    "value": 4
  }
]

И вот рабочий пример https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a

function getArgs(args) {
    var argsObj = {};

    var argList = /\(([^)]*)/.exec(args.callee)[1];
    var argCnt = 0;
    var tokens;

    while (tokens = /\s*([^,]+)/g.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}

Как я обычно это делаю:

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

Вы можете даже ссылаться на аргументы по имени функции, например:

name.arguments;

Надеюсь это поможет!

Этот пакет использует recast для создания AST, а затем из них собираются имена параметров, что позволяет ему поддерживать сопоставление с образцом, аргументы по умолчанию, функции стрелок и другие функции ES6.

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

Я изменил версию, взятую из 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({})

Я приведу вам короткий пример ниже:

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

Вы можете получить доступ к значениям аргумента, переданным функции, используя свойство 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]);    

Каким бы ни было решение, оно не должно нарушать странные функции, чьи toString() выглядит так же странно:

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

скриншот из консоли

Кроме того, зачем использовать сложные регулярные выражения? Это можно сделать так:

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

Это работает везде с каждой функцией, и единственным регулярным выражением является удаление пробелов, которое даже не обрабатывает всю строку из-за .split трюк.

Для меня это очень хорошо:

      function parseArgs(fn, options = {
    ignoreWithDefault: true,
    ignoreWithComment: true
}) {

    const regex = /\((.|\s)*\)/gm;

    const m = regex.exec(fn.toString())

    let normalised = m[0]
    if (options.ignoreWithComment) {
        normalised = normalised.replaceAll(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "")
    }
    return normalised.replaceAll(/[ ()]/gm, "")
        .split(/[\r|\n|,]/gm)
        .filter((item) => {
            if(item === "") return false
            if(options.ignoreWithDefault){
                return !item.includes("=");
            }
            return true
        })
}

const a = (a = (4/5), b, c, D4, /*fasdf */g5,
    //
    //g
    f_123,
    asdf
) => {
}

console.log(parseArgs(a))

Примечание: если вы хотите использовать деструктуризацию параметров ES6 в верхнем решении, добавьте следующую строку.

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

Это довольно легко.

На первом есть устаревший arguments.callee - ссылка на вызываемую функцию. Во-вторых, если у вас есть ссылка на вашу функцию, вы можете легко получить их текстовое представление. В-третьих, если вы вызываете свою функцию в качестве конструктора, вы также можете иметь ссылку через yourObject.constructor. NB: Первое решение устарело, поэтому, если вы не можете его не использовать, вы должны также подумать об архитектуре вашего приложения. Если вам не нужны точные имена переменных, просто используйте внутреннюю переменную функции 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 Джек!

Другие вопросы по тегам