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

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

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

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

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


person vikasde    schedule 17.06.2009    source источник
comment
Спасибо всем. После поиска я нашел решение на SO: stackoverflow.com/questions/914968/ Он использует регулярное выражение для получения имени параметра. Это, наверное, не лучшее решение, но у меня оно работает.   -  person vikasde    schedule 17.06.2009


Ответы (31)


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

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 не является массивом. Он похож на массив, но не имеет никаких свойств массива, кроме длины. Например, у него нет метода pop. Однако его можно преобразовать в настоящий массив:

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

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

var args = Array.slice(arguments);
person Jack Allan    schedule 29.03.2012
comment
Обратите внимание, что это решение может не работать из-за комментариев и пробелов - например: var fn = function(a /* fooled you)*/,b){}; приведет к ["a", "/*", "fooled", "you"] - person bubersson; 02.02.2013
comment
Это и угловое решение @Lambder получают имена параметров, но как получить значения? - person CWSpear; 03.05.2013
comment
@bubersson Вы можете использовать это в сочетании с кодом AngularJS, чтобы удалить комментарии, показанные в ответе Ламбдера, чтобы избежать нарушения этого комментария. - person JBarnes; 03.07.2013
comment
@JBarnes Да! Я также давно добавил этот комментарий внизу этой страницы. - person bubersson; 03.07.2013
comment
Обновлен мой пример, чтобы включить регулярное выражение для удаления комментариев - person Jack Allan; 08.07.2013
comment
Я изменил функцию, чтобы она возвращала пустой массив (вместо нуля), когда нет никаких аргументов - person B T; 06.08.2013
comment
Мне любопытно, почему STRIP_COMMENTS выходит за рамки функции? Я понимаю, что это работает, но есть ли причина так делать? Отличный инструмент! - person christianbundy; 11.05.2014
comment
Компиляция регулярного выражения требует затрат, поэтому вы не хотите компилировать сложные регулярные выражения более одного раза. Вот почему это делается вне функции - person Jack Allan; 11.05.2014
comment
Регулярное выражение удаления комментариев можно упростить, заметив, что: '[\ s \ S]' можно заменить на '.' Это связано с тем, что класс символов, который соответствует любому символу в наборе или его дополнению, будет соответствовать любому символу. Все эти регулярные выражения соответствуют любому одиночному символу: '[\ s \ S]', [\ w \ W] ',' [\ d \ D] 'или'. '. Итак, /((\/\/.*$)|(\/\*[\s\S ]*?\*\/))/mg можно заменить на /((\/\/.*$) | (\ /*.*? * \ /)) / мг; jsfiddle.net/tgoneil/6K57R - person tgoneil; 11.05.2014
comment
ИСПРАВЛЕНИЕ: собирался изменить регулярное выражение с помощью модификатора / s, который Perl позволяет так '.' также может соответствовать новой строке. Это необходимо для многострочных комментариев внутри / * * /. Оказывается, регулярное выражение Javascript не поддерживает модификатор / s. Исходное регулярное выражение с использованием [/ s / S] действительно соответствует символам новой строки. СООО, прошу проигнорировать предыдущий комментарий. - person tgoneil; 12.05.2014
comment
Я сделал jsperf между этим ответом и модом ответа Angular JS - ответ @ jack-allen побеждает Angular: jsperf.com/get-function-parameter-names. - person andes; 05.04.2015
comment
@andes Обратите внимание, что вы включаете компиляцию регулярных выражений в свои тесты. Это следует делать только один раз. Ваши результаты могут отличаться, если вы переместите компиляцию регулярного выражения на этап настройки вместо теста. - person Jack Allan; 07.04.2015
comment
Обновлено с помощью взлома, чтобы код работал с параметрами ES6 по умолчанию. - person Jack Allan; 26.06.2015
comment
Можем ли мы узнать имена фактических аргументов, переданных в функцию? как func (a, b, c, d); когда мы вызываем это, я хочу, чтобы на выходе были напечатаны a, b, c, d - person Shishir Arora; 09.07.2015
comment
@Shishir, вот что возвращает эта функция - person Jack Allan; 18.08.2015
comment
stackoverflow.com/questions/31317524/ - person Shishir Arora; 18.08.2015
comment
Стрелочные функции ES6, имеющие один аргумент, например (a = ›a * 10), не могут дать желаемый результат. - person Avinash; 02.04.2017
comment
материал es6 хорош, но в env prod мы все еще переносим код в es5, что означает, что параметры по умолчанию находятся в функции, объявленной сверху (см. babel) - person Robin F.; 16.05.2017
comment
@Jack Allan Я действительно понятия не имею - ответ не на вопрос. Вопрос в том, как получить параметр names внутри функции, когда функция invoked вместо defined. В настоящее время я столкнулся с той же проблемой (пытался определить метод по имени параметра, а затем передать значение параметра методу, ориентированному на имя). Но, как ни странно, за него проголосовало большинство людей. Возможно, он хорошо закодирован, но на самом деле не помогает решить вопрос. Спасибо. - person Hearen; 28.01.2018
comment
В chrome приведенная выше функция getParamNames (...) просто возвращает массив со строкой 'e' внутри. Просто попробовал и получил 0: "e" в консоли разработчика. Есть идеи, почему? - person Magnus; 18.04.2018
comment
Я бы не стал использовать это в продакшене; При таком подходе у параметров по умолчанию слишком много проблем. Например, getParamNames(function (a = function (b = 4, c = 5) {}){}) сообщает ["a", "c"]. Более безопасным способом было бы использовать абстрактное синтаксическое дерево, а не регулярные выражения. - person Michael Theriot; 15.07.2018
comment
Как легко это преодолеть. getParamNames(function (b = (2), c = 2) {}) = ›["b", "=", "(2"] - person Mr.DeleteMyMessages; 22.12.2018
comment
_1 _... для значений параметров, обновлено для отражения ES6 и более поздних версий. Просто говорю'. - person Tim Pozza; 28.08.2019
comment
Некоторые люди сказали, что это на самом деле не отвечает на вопрос. И актуальный вопрос заключается в том, как я могу узнать имена переменных, которые использовались в вызове функции, которая вызвала мою функцию? Я не предполагал, что это будет вопрос, потому что это действительно плохая идея для любого языка, чтобы такое было возможно. Для начала вызов функции можно было бы сделать с помощью литералов вместо переменных. Кроме того, любой язык, который позволяет это, не позволит программисту скрывать детали реализации функции. Это делает любое реальное программирование совершенно непрактичным. Итак, чтобы - person Jack Allan; 28.08.2019
comment
(Продолжение) Итак, всем, кто нашел мой ответ и ожидал, что он ответит на этот вопрос, а не на то, что я предполагал, я предлагаю такой совет: то, что вы пытаетесь сделать, безусловно, плохая идея, и я не сомневаюсь, что, даже если бы это было возможно - а, к счастью, это не так - для этого нет никаких веских причин. Пожалуйста, переоцените свою проблему, потому что вы не решаете ее правильно. - person Jack Allan; 28.08.2019

Ниже приведен код, взятый из 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;
}
person Lambder    schedule 24.08.2012
comment
@apaidnerd, очевидно, с кровью демонов и порождением сатаны. Регулярное выражение ?! Было бы здорово, если бы в JS был встроенный способ, не так ли. - person Aditya M P; 24.07.2013
comment
@apaidnerd, верно! Просто подумал - как, черт возьми, это реализовано? На самом деле я думал об использовании functionName.toString (), но надеялся на что-то более элегантное (и, возможно, более быстрое) - person sasha.sochka; 29.08.2013
comment
@ sasha.sochka, пришла сюда, задавшись вопросом о том же самом, после того, как поняла, что не существует встроенного способа получения имен параметров с помощью javascript - person Hart Simha; 12.10.2014
comment
чтобы сэкономить время людей, вы можете получить эту функцию из angular через annotate = angular.injector.$$annotate - person Nick; 03.02.2015
comment
Я сделал jsperf между модом этого ответа и выбранным ответом Джека-Аллана - ответ Джек-Аллена побеждает Angular: jsperf.com/get-function-parameter-names - person andes; 05.04.2015
comment
Если вы уже используете Angular, вы можете использовать его $injector service: $injector.invoke(function(serviceA){}); - person weltschmerz; 19.11.2015
comment
Я буквально искал в Интернете эту тему, потому что мне было любопытно, как это удалось в Angular ... теперь я знаю и знаю слишком много! - person Steven Hunt; 25.08.2016
comment
@AdityaMP ваш первый комментарий сделал мой день: D - person captain-yossarian; 11.05.2021

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

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>

person humbletim    schedule 02.07.2015
comment
Это прерывается, когда присутствуют однострочные комментарии. Попробуйте это: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace - person Merlyn Morgan-Graham; 26.03.2016
comment
Хорошая уловка - я обновил пример предложенным вами исправлением, а также добавил соответствующий тестовый пример во фрагмент кода. Спасибо! - person humbletim; 16.05.2016
comment
Вероятно, вам следует заменить func + '' на Function.toString.call(func), чтобы защититься от случая, когда функция имеет настраиваемую реализацию .toString (). - person Paul Go; 04.11.2016
comment
Интересно, как от этого защититься, и хотел бы учесть ваше предложение. Значения, не являющиеся функциональными, все равно придется приводить - как вы думаете, стоит стремиться к компактности: (/^f/.test(typeof func) ? $args.toString.call(func) : func+''), или в этом случае лучше просто сформулировать это: (typeof func === 'function' ? Function.toString.call(func) : func+'')? - person humbletim; 08.11.2016
comment
толстые стрелки = ›.split(/\)[\{=]/, 1)[0] - person Matt; 05.12.2016
comment
это разбивает деструктурированные объекты (например, ({ a, b, c })) на все параметры внутри деструктурирования. чтобы не повредить деструктурированные объекты, измените последний .split на: .split(/,(?![^{]*})/g) - person Michael Auderer; 22.07.2019
comment
Это также не будет работать, если есть строковые значения по умолчанию, содержащие // или / * - person skerit; 01.04.2020
comment
У меня есть функция с разрушающим параметром как функция ({test = '22'} = {}) {return test;}. $ args () дает мне результат ['{test']. не могли бы вы это исправить. Спасибо :) - person Kai Lehmann; 28.03.2021

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

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

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

["hi", "you"]
person bubersson    schedule 02.02.2013
comment
@AlexMills Я заметил одну вещь: в спецификации для стрелочных функций сказано, что их не следует рассматривать как «функции». Это означает, что для этого было бы неприемлемо соответствовать функциям массива. 'This' не устанавливается таким же образом, и их также нельзя вызывать как функции. Я узнал это на собственном горьком опыте. ($ myService) = ›$ myService.doSomething () выглядит круто, но это неправильное использование функций массива. - person Andrew T Finnell; 06.09.2016

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

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 / и т. Д.). Также, если вам нужно поменять реализации, вы можете сделать это из одного места. Все это возможно благодаря объектам JS Proxy.

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

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

person James Drew    schedule 07.01.2017

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

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

person Domino    schedule 20.08.2015
comment
Я думаю, что обычно случается что-то вроде этого: отладка / ведение журнала, какой-то декоратор, который делает забавные вещи (технический термин ????), или создание инфраструктуры внедрения зависимостей для ваших приложений, чтобы вводить автоматически на основе имени аргумента (вот как angular работает). Другой действительно интересный вариант использования - promisify-node (это библиотека, которая принимает функцию, которая обычно принимает обратный вызов, а затем преобразует ее в Promise). Они используют это, чтобы найти общие имена для обратных вызовов (например, cb / callback / и т. Д.), А затем они могут проверить, является ли функция асинхронной или синхронизированной, прежде чем оборачивать ее. - person James Drew; 07.01.2017
comment
См. этот файл для их парсера. Это немного наивно, но в большинстве случаев справляется. - person James Drew; 07.01.2017
comment
Интересно, я удивлен, что такая библиотека привлекла внимание. Что ж, есть несколько открытых вопросов о проблемных ситуациях, подобных тем, которые я описал. Как я уже сказал, если это для целей отладки, это нормально, но полагаться на строковое преобразование функций в производственных средах слишком рискованно. - person Domino; 08.01.2017
comment
Забавно, что вы можете заставить все функции работать так, как если бы они были анонимными встроенными модулями, выполнив это: Function.prototype.toString = function () { return 'function () { [native code] }'; }; - person Domino; 08.01.2017
comment
Согласен, это немного беспорядочно. Лучшее и наиболее простое использование - внедрение зависимостей. В этом случае вы владеете кодом и можете обрабатывать именование и другие области кода. Я думаю, что именно здесь он будет наиболее полезен. В настоящее время я использую esprima (вместо регулярного выражения) и ES6 Proxy (ловушка конструктора и apply trap) и Reflection для обработки DI для некоторых из моих модулей. Это достаточно прочно. - person James Drew; 08.01.2017
comment
Некоторые из меня хотели бы включить это в приложение angular и посмотреть, сколько времени потребуется нашим разработчикам, чтобы отладить его. ???????? Может быть хорошим вопросом для длинного интервью! ???? - person James Drew; 08.01.2017

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

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

or

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

или для однострочной функции в ECMA6

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

__

Допустим, у вас есть функция

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

Приведенный ниже код вернет "abc,def,ghi,jkl"

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

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

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

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

__

Объяснение

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

Это создает регулярное выражение с new RegExp('(?:'+Function.name+'\\s*|^)\\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, '')

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


Это также теперь поддерживает функции чтения и понимания стрелок (=>), такие как f = (a, b) => void 0;, в которых Function.toString() будет возвращать (a, b) => void 0 вместо function f(a, b) { return void 0; } нормальной функции. Исходное регулярное выражение могло бы вызвать ошибку, но теперь оно учтено.

Изменение было с new RegExp(Function.name+'\\s*\\((.*?)\\)') (/Function\s*\((.*?)\)/) на new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') (/(?:Function\s*|^)\((.*?)\)/)


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

person ZomoXYZ    schedule 31.08.2016
comment
довольно мило. одна строка, обрабатывает стрелочные функции, не имеет внешних зависимостей и охватывает более чем достаточное количество граничных случаев, по крайней мере, для моего предполагаемого использования. Если вы можете сделать некоторые разумные предположения о сигнатурах функций, с которыми вы будете иметь дело, это все, что вам нужно. Благодарность! - person charlie roberts; 11.11.2019
comment
Не работает с этой простой стрелочной функцией: f = (a, b) => void 0; getParameters(f) я получаю TypeError: Cannot read property '1' of null - person A T; 27.11.2019
comment
@AT Я только что обновил ответ, чтобы исправить вашу проблему. - person ZomoXYZ; 27.11.2019
comment
Спасибо ... но имейте в виду, что скобки больше не требуются, поэтому вы можете делать такие вещи, как getParameters(a => b => c => d => a*b*c*d), который с вашим кодом по-прежнему дает TypeError: Cannot read property '1' of null ... тогда как этот работает stackoverflow.com/a/29123804 - person A T; 29.11.2019
comment
Не работает, если функция имеет значения по умолчанию (role, name = bob). Извлеченный параметр name = bob вместо ожидаемого имени. - person Dmitri; 23.03.2020

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

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

person Itay Maman    schedule 01.07.2018
comment
как насчет ценностей? - person eran otzap; 05.10.2019

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

//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
});
person hugoware    schedule 17.06.2009
comment
У меня есть много предопределенных функций, которые вызываются со стандартными параметрами вместо одного объекта. На изменение всего потребуется много времени. - person vikasde; 17.06.2009
comment
Этот ответ не является ответом на исходный вопрос, который был точно определен. Он показывает решение совершенно другой проблемы. Исходный вопрос относится к технике, которую AngularJS использует для внедрения зависимостей. Имена аргументов имеют смысл, поскольку они соответствуют зависимостям модуля, которые автоматически предоставляет DI. - person Lambder; 24.08.2012

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

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

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

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

person Ionuț G. Stan    schedule 17.06.2009

Взяв ответ от @ 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;
    }
}
person thelastshadow    schedule 18.03.2015
comment
Спасибо, ваш единственный в этой теме, который у меня сработал. - person A T; 27.11.2019

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

alert(doSomething.length);
person Ólafur Waage    schedule 17.06.2009

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

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

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

name.arguments;

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

person Cody    schedule 20.08.2012
comment
А где названия параметров функции? - person Andrej; 22.01.2013
comment
Ах ... Ты хочешь сказать, что хэш-форма? Как будто: var args = name.arguments; console.log('I WANNa SEE', args); выводит что-то вроде {arg1: {...}, arg2: 'string'}? Это может прояснить ситуацию: (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Функциональные аргументы представляют собой объект типа «Массив», имеющий перечислимые индексы. Я не думаю, что хеш-отображение возможно, но вы можете просто передать объектный литерал, что является хорошей практикой, если у вас в любом случае более 4 параметров. - person Cody; 17.12.2013

Чтобы ответить на этот вопрос, нужно сделать 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/5763a7130077a

person gafi    schedule 17.06.2016

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

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

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

person SnailCrusher    schedule 25.12.2016

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

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

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

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

Поскольку метаданные все еще являются ранним предложением, Reflect-metadata, иначе Reflect.getMetadata не будет определен.

npm install reflect-metadata

Вы можете использовать его следующим образом:

const AnyDecorator = () : MethodDecorator => {
    return target => { }
}

class Person{
    @AnyDecorator()
    sayHello(other: Person){}
}
const instance = new Person();
// This returns: Function
const funcType = Reflect.getMetadata('design:type', instance.sayHello);
// Returns an array of types, here it would be: [Person]
const funcParams = Reflect.getMetadata('design:paramtypes', instance.sayHello);

Например, в новых версиях Angular это используется для определения того, что вводить - ›https://stackoverflow.com/a/53041387/1087372

person SanBen    schedule 02.10.2020
comment
Помещает ли он список имен параметров функции внутри функции? - person Melab; 17.06.2021
comment
@Melab getMetadata ('design: paramtypes', x) вернет только типы, например [String] - person SanBen; 23.06.2021

Ниже я приведу небольшой пример:

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();
person myzhou    schedule 19.05.2017
comment
Этот пример предназначен только для того, чтобы получить для вас имя параметров. - person myzhou; 19.05.2017
comment
Этот ответ полезен, но он не отвечает на вопрос. Я проголосовал, потому что это решило мою проблему, но я думаю, что его следует перенести в более подходящее место. Возможно, поискать связанные вопросы? - person chharvey; 04.10.2017

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

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

person user3436035    schedule 14.10.2017

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

person loretoparisi    schedule 29.11.2017

Вы можете получить доступ к значениям аргументов, переданных функции, используя свойство «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]);    
person letronje    schedule 17.06.2009
comment
Разве аргументы не устарели? См. Предложение Ионута Г. Стэна выше. - person vikasde; 17.06.2009
comment
Викасде прав. Доступ к свойству arguments экземпляра функции устарел. См. developer.mozilla.org/en/Core_JavaScript_1.5_Reference/. - person Ionuț G. Stan; 18.06.2009

Это довольно просто.

Во-первых, это устаревшая 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: Джеку Аллану было предложено немного лучшее решение. Джек Джек!

person Alex Yaroshevich    schedule 01.02.2013
comment
Это могло бы быть еще проще, если бы вы использовали SomeFuncName вместо arguments.callee (оба указывают на сам объект функции). - person Raphael Schweikert; 20.06.2013

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

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

снимок экрана с консоли

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

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

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

person Camilo Martin    schedule 19.05.2015

Хорошо, старый вопрос с множеством адекватных ответов. вот мое предложение, которое не использует регулярное выражение, за исключением черной задачи удаления пробелов. (Я должен отметить, что функция "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);
person unsynchronized    schedule 03.08.2016

Вот один из способов:

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

person Ates Goral    schedule 17.06.2009
comment
Цикл while приводит к бесконечному циклу с предоставленным кодом (по состоянию на 20171121at1047EDT) - person George 2.0 Hope; 21.11.2017
comment
@ George2.0Hope Спасибо, что указали на это. Я обновлю ответ. - person Ates Goral; 21.11.2017
comment
args.toSource не является функцией (строка 20) ... но даже если вы измените его на: console.log (args.toString ()); ... вы получите ... [объект Object] ... лучше, если вы это сделаете: console.log (JSON.stringify (args)); - person George 2.0 Hope; 22.11.2017
comment
С 2009 года многое изменилось! - person Ates Goral; 23.11.2017
comment
в случае, если кто-то появится здесь для React, эта функция, которая прекрасна, не будет работать в строгом режиме. - person Individual11; 08.05.2018

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

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

Я хотел бы предложить решение, которое поддерживает функции стрелок, например, я использовал эту статью для базового регулярного выражения и https://davidwalsh.name/javascript-arguments и добавленная поддержка стрелочных функций

(arg1,arg2) => {}

or

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;
  });
}
person Denis Ponomarev    schedule 23.10.2020
comment
похоже, не работает для встроенных комментариев, содержащих закрывающую скобку, например. function foo(x, y /* FIXME: (temp), z */, w) { - person Dan O; 25.10.2020
comment
Я пытался редактировать, но предложенная очередь редактирования заполнена. Вы можете исправить проблему, о которой сообщил Дэн О, переместив замену (/\/*.**\//, '') в операторе возврата снизу в конец объявления string = func.toString () рядом с Топ. Обязательно сохраните trim () в операторе возврата. - person Michael A. Vickers; 17.01.2021

изображение значения строки параметра функции динамически из 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>
person KAUSHAL J. SATHWARA    schedule 29.07.2015

Попробуйте вручную:

function something(arg1, arg2) {
  console.log ( arg1 + arg2 );
}
person Harun Or Rashid    schedule 15.01.2019

person    schedule
comment
Если я не ошибаюсь, в версии вашего браузера первое условие в FUNC_ARGS будет работать как для стрелочных, так и для традиционных функций, поэтому вам не нужна вторая часть, и вы можете избавиться от зависимости от ARROW. - person pwilcox; 02.09.2018
comment
Отлично! Я искал подобное решение, в котором для синтаксиса ES6 используется синтаксический анализатор. Я планирую использовать это для создания сопоставления «реализует интерфейс», потому что простое использование function.length имеет ограничения с параметрами по умолчанию, и я хотел иметь возможность утверждать параметры отдыха. - person cue8chalk; 17.11.2018
comment
Стоит отметить, что пятый тестовый пример, содержащий круглые скобки в значении по умолчанию, в настоящее время не выполняется. Мне жаль, что мое регулярное выражение-фу было достаточно сильным, чтобы его исправить, извините! - person Dale Anderson; 12.03.2019

person    schedule
comment
Это не работает во многих случаях, включая любой код с символом новой строки (\ r \ n) и любой код функции, который включает символы ( и ) внутри своего тела функции! например: myFunc(p1, p2) { if(p1>0){} } - person S.Serpooshan; 01.07.2021