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

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

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

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

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

Ответ 1

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

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

Edit

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

var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

Я говорю большинство случаев, потому что есть некоторые вещи, которые будут его отключать.

function (a=4*(5/3), b) {} // returns ['a']

Edit: Я также отмечаю, что vikasde также хочет получить значения параметров в массиве. Это уже указано в локальной переменной с аргументами.

выдержка из https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments:

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

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

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

var args = Array.slice(arguments);

Ответ 2

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

Ответ 3

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

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>

Ответ 4

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

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

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

["hi", "you"]

Ответ 5

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

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

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

Ответ 6

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

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

Ответ 7

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

Это привело меня к этому вопросу, но никаких встроенных решений. Это привело меня к этому ответу, который объясняет, что arguments только устарели вне функции, поэтому мы больше не можем использовать myFunction.arguments или получаем:

TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them

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

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

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

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

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

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


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

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

Версия Node.js (не выполняется до тех пор, пока StackOverflow не поддерживает Node.js):

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

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

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

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

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

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

	object[func] = getArguments(func);

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

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

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

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

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

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

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

Ответ 8

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

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

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

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

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

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

__

Объяснение

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

Это создает Regular Exponent с помощью 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] вернет первую Capture Group, найденную в Regular Exponent ((.*?)).

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

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


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

Ответ 12

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

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

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

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

Ответ 13

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

alert(doSomething.length);

Ответ 14

Взяв ответ из @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;
    }
}

Ответ 15

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

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

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

name.arguments;

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

Ответ 16

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

Ответ 17

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

Ответ 18

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

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

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

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

Ответ 19

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

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

Эта функция очень полезна для целей отладки, например: logging вызывает функцию с ее параметрами, значениями и аргументами по умолчанию.

Я потратил некоторое время на это вчера, взломав правильный 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 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 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\'' ] ]

Ответ 20

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

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

Ответ 21

Я изменил версию, взятую из AngularJS, которая реализует механизм инъекции зависимостей для работы без Angular. Я также обновил regex 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({})

Ответ 22

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

Ответ 23

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

Ответ 24

Здесь один из способов:

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

Ответ 25

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

Вначале существует устаревшая arguments.callee - ссылка на вызываемую функцию. Во втором случае, если у вас есть ссылка на вашу функцию, вы можете легко получить их текстовое представление. На третьем, если вы вызываете свою функцию в качестве конструктора, вы также можете иметь ссылку через свой объектObject.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 Jack!

Ответ 26

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

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

screenshot from console

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

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

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

Ответ 27

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

Ответ 28

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

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

Ответ 29

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

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

Ответ 30

функциональное значение строкового значения динамического изображения динамически из 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>