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


301

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

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

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

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


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

8
давайте продолжим и отметим это как ответили приятель. новые ответы не приходят.
Мэтью Грейвс

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

Ответы:


323

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

var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
  var fnStr = func.toString().replace(STRIP_COMMENTS, '');
  var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
  if(result === null)
     result = [];
  return result;
}

Пример использования:

getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []

Редактировать :

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

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

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

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

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

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

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

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

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

var args = Array.slice(arguments);

12
Обратите внимание, что это решение может дать сбой из-за комментариев и пробелов - например: var fn = function(a /* fooled you)*/,b){};приведет к ["a", "/*", "fooled", "you"]
bubersson

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

2
Компиляция регулярного выражения требует определенных затрат, поэтому вам нужно избегать компиляции сложных регулярных выражений более одного раза. Вот почему это делается вне функции
Джек Аллан

2
ИСПРАВЛЕНИЕ: собирался изменить регулярное выражение с помощью модификатора / s, который perl позволяет, так что '.' также может соответствовать новой строке. Это необходимо для многострочных комментариев внутри / * * /. Оказывается, регулярное выражение Javascript не допускает модификатор / s. Исходное регулярное выражение с использованием [/ s / S] соответствует символам новой строки. СООО, пожалуйста, не обращайте внимания на предыдущий комментарий.
tgoneil

1
@andes Обратите внимание, что вы включаете компиляцию регулярных выражений в свои тесты. Это должно быть сделано только один раз. Ваши результаты могут отличаться, если вы переместите компиляцию регулярных выражений на шаг настройки вместо теста
Джек Аллан

123

Ниже приведен код, взятый из AngularJS, который использует технику для своего механизма внедрения зависимостей.

И вот объяснение этого взято от http://docs.angularjs.org/tutorial/step_05

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

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

/**
 * @ngdoc overview
 * @name AUTO
 * @description
 *
 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}.
 */

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function annotate(fn) {
  var $inject,
      fnText,
      argDecl,
      last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
        arg.replace(FN_ARG, function(all, underscore, name){
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else if (isArray(fn)) {
    last = fn.length - 1;
    assertArgFn(fn[last], 'fn')
    $inject = fn.slice(0, last);
  } else {
    assertArgFn(fn, 'fn', true);
  }
  return $inject;
}

40
@apaidnerd с кровью демонов и порождением сатаны, очевидно. Regex ?! Было бы здорово, если бы в JS был встроенный способ, не так ли?
депутат Адитья

6
@apaidnerd, это правда! Просто подумал - как, черт возьми, это реализовано? На самом деле я думал об использовании functionName.toString (), но надеялся на что-то более элегантное (и, возможно, более быстрое)
sasha.sochka

2
@ sasha.sochka, пришла сюда, задаваясь вопросом о том же самом, поняв, что не было встроенного способа получить имена параметров с помощью javascript
Харт Симха,

14
чтобы сэкономить время людей, вы можете получить эту функцию с помощью углов annotate = angular.injector.$$annotate
Nick

3
Я буквально отправился на поиски в Интернете этой темы, потому что мне было любопытно, как это сделал Angular ... теперь я знаю, а также знаю слишком много!
Стивен Хант

40

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

function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

Сокращенный результат теста (полные тестовые примеры прилагаются ниже):

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


Это прерывается, когда присутствуют однострочные комментарии. Попробуйте это: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Мерлин Морган-Грэм

4
Вам, вероятно, следует заменить func + ''на Function.toString.call(func)для защиты от случая, когда функция имеет собственную реализацию .toString ().
Пол Го

1
толстые стрелки =>.split(/\)[\{=]/, 1)[0]
Мэтт

1
это разделяет деструктурированные объекты (например ({ a, b, c })) на все параметры внутри деструктуризации. чтобы сохранить не поврежденные объекты, измените последнее .splitна: .split(/,(?![^{]*})/g)
Michael Auderer

1
Это также не будет работать, если есть строковые значения по умолчанию, которые содержат «//» или «/ *»
скерит

23

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

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

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

["hi", "you"]

1
@AlexMills Одна вещь, которую я заметил, заключается в том, что в спецификации для функций со стрелками говорится, что они не должны рассматриваться как «функции». Это означает, что это не подходит для соответствия функциям массива. 'This' не устанавливается таким же образом, и они также не должны вызываться как функции. Это было то, чему я научился трудным путем. ($ myService) => $ myService.doSomething () выглядит круто, но это неправильное использование функций массива.
Эндрю Финнелл

20

Многие ответы здесь используют регулярные выражения, это хорошо, но он не слишком хорошо обрабатывает новые дополнения к языку (например, функции стрелок и классы). Также следует отметить, что если вы используете какую-либо из этих функций в минимизированном коде, она пойдет 🔥. Он будет использовать любое минимизированное имя. Angular обходит это, позволяя передавать упорядоченный массив строк, который соответствует порядку аргументов при регистрации их в контейнере DI. Итак, с решением:

var esprima = require('esprima');
var _ = require('lodash');

const parseFunctionArguments = (func) => {
    // allows us to access properties that may or may not exist without throwing 
    // TypeError: Cannot set property 'x' of undefined
    const maybe = (x) => (x || {});

    // handle conversion to string and then to JSON AST
    const functionAsString = func.toString();
    const tree = esprima.parse(functionAsString);
    console.log(JSON.stringify(tree, null, 4))
    // We need to figure out where the main params are. Stupid arrow functions 👊
    const isArrowExpression = (maybe(_.first(tree.body)).type == 'ExpressionStatement');
    const params = isArrowExpression ? maybe(maybe(_.first(tree.body)).expression).params 
                                     : maybe(_.first(tree.body)).params;

    // extract out the param names from the JSON AST
    return _.map(params, 'name');
};

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

// I usually use mocha as the test runner and chai as the assertion library
describe('Extracts argument names from function signature. 💪', () => {
    const test = (func) => {
        const expectation = ['it', 'parses', 'me'];
        const result = parseFunctionArguments(toBeParsed);
        result.should.equal(expectation);
    } 

    it('Parses a function declaration.', () => {
        function toBeParsed(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses a functional expression.', () => {
        const toBeParsed = function(it, parses, me){};
        test(toBeParsed);
    });

    it('Parses an arrow function', () => {
        const toBeParsed = (it, parses, me) => {};
        test(toBeParsed);
    });

    // ================= cases not currently handled ========================

    // It blows up on this type of messing. TBH if you do this it deserves to 
    // fail 😋 On a tech note the params are pulled down in the function similar 
    // to how destructuring is handled by the ast.
    it('Parses complex default params', () => {
        function toBeParsed(it=4*(5/3), parses, me) {}
        test(toBeParsed);
    });

    // This passes back ['_ref'] as the params of the function. The _ref is a 
    // pointer to an VariableDeclarator where the ✨🦄 happens.
    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ({it, parses, me}){}
        test(toBeParsed);
    });

    it('Parses object destructuring param definitions.' () => {
        function toBeParsed ([it, parses, me]){}
        test(toBeParsed);
    });

    // Classes while similar from an end result point of view to function
    // declarations are handled completely differently in the JS AST. 
    it('Parses a class constructor when passed through', () => {
        class ToBeParsed {
            constructor(it, parses, me) {}
        }
        test(ToBeParsed);
    });
});

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

class GuiceJs {
    constructor() {
        this.modules = {}
    }
    resolve(name) {
        return this.getInjector()(this.modules[name]);
    }
    addModule(name, module) {
        this.modules[name] = module;
    }
    getInjector() {
        var container = this;

        return (klass) => {
            console.log(klass);
            var paramParser = new Proxy({}, {
                // The `get` handler is invoked whenever a get-call for
                // `injector.*` is made. We make a call to an external service
                // to actually hand back in the configured service. The proxy
                // allows us to bypass parsing the function params using
                // taditional regex or even the newer parser.
                get: (target, name) => container.resolve(name),

                // You shouldn't be able to set values on the injector.
                set: (target, name, value) => {
                    throw new Error(`Don't try to set ${name}! 😑`);
                }
            })
            return new klass(paramParser);
        }
    }
}

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

class App {
   constructor({tweeter, timeline}) {
        this.tweeter = tweeter;
        this.timeline = timeline;
    }
}

class HttpClient {}

class TwitterApi {
    constructor({client}) {
        this.client = client;
    }
}

class Timeline {
    constructor({api}) {
        this.api = api;
    }
}

class Tweeter {
    constructor({api}) {
        this.api = api;
    }
}

// Ok so now for the business end of the injector!
const di = new GuiceJs();

di.addModule('client', HttpClient);
di.addModule('api', TwitterApi);
di.addModule('tweeter', Tweeter);
di.addModule('timeline', Timeline);
di.addModule('app', App);

var app = di.resolve('app');
console.log(JSON.stringify(app, null, 4));

Это выводит следующее:

{
    "tweeter": {
        "api": {
            "client": {}
        }
    },
    "timeline": {
        "api": {
            "client": {}
        }
    }
}

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

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

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


13

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

Параметры функции на самом деле хранятся в похожем на массив объекте, который называется arguments, где первый аргумент arguments[0], второй - arguments[1]и так далее. Запись имен параметров в скобках можно рассматривать как сокращенный синтаксис. Это:

function doSomething(foo, bar) {
    console.log("does something");
}

...такой же как:

function doSomething() {
    var foo = arguments[0];
    var bar = arguments[1];

    console.log("does something");
}

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

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

Вот еще хуже и более распространенная ситуация. Если функция имеет более 3 или 4 аргументов, может быть логичным передать ей объект, с которым легче работать.

function saySomething(obj) {
  if(obj.message) console.log((obj.sender || "Anon") + ": " + obj.message);
}

saySomething({sender: "user123", message: "Hello world"});

В этом случае сама функция сможет прочитать полученный объект, найти его свойства и получить их имена и значения, но попытка разобрать строковое представление функции даст только «obj» для параметров, что не полезно вообще.


1
Я думаю, что причина для чего-то вроде этого обычно: отладка / ведение журнала, какой-то декоратор, который делает прикольные вещи (технический термин 😁), или создание инфраструктуры внедрения зависимостей для ваших приложений для автоматического внедрения на основе имени аргумента (это то, как угловатый работает). Другой действительно интересный случай использования - это Promisify-Node (это библиотека, которая принимает функцию, которая обычно принимает обратный вызов, а затем преобразует ее в Promise). Они используют это, чтобы найти общие имена для обратных вызовов (например, cb / callback / и т. Д.), А затем они могут проверить, является ли функция асинхронной или синхронизированной, прежде чем обернуть ее.
Джеймс Дрю

Смотрите этот файл для их парсера . Это немного наивно, но справляется с большинством случаев.
Джеймс Дрю

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

На «забавном» sidenote вы можете заставить все функции работать так, как если бы они были анонимными встроенными программами, запустив это:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino

1
Согласитесь, немного грязно с этим обращаться. Лучшее и наиболее простое использование - внедрение зависимостей. В этом случае вы владеете кодом и можете обрабатывать наименования и другие области кода. Я думаю, что именно здесь это будет наиболее полезным. В настоящее время я использую esprima (вместо regex) и ES6 Proxy( конструктор trap и apply trap ) и Reflectionдля обработки DI для некоторых из моих модулей. Это достаточно твердо.
Джеймс Дрю

10

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

	object[func] = getArguments(func);

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

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

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

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

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

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

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

Полный рабочий пример:

https://repl.it/repls/SandybrownPhonyAngles

Версия браузера (обратите внимание, что он останавливается на первом комплексном значении по умолчанию):

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

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

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

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

	object[func] = getArguments(func);

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

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

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

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

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

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

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

Полный рабочий пример:

https://repl.it/repls/StupendousShowyOffices


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

Это круто! Я искал решение, подобное этому, которое использует синтаксический анализатор для покрытия синтаксиса ES6. Я планирую использовать это для создания jest-средства «реализует интерфейс», потому что простое использование function.length имеет ограничения с параметрами по умолчанию, и я хотел иметь возможность утверждать остальные параметры.
cue8chalk

Стоит отметить, что пятый контрольный пример, содержащий скобки в значении по умолчанию, в настоящее время не выполняется. Я хотел бы, чтобы мое регулярное выражение было достаточно сильным, чтобы исправить, извините!
Дейл Андерсон

8

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

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

или

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

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

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

__

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

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

Код ниже вернется "abc,def,ghi,jkl"

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

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, '')

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


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

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


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


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

Не работаю с этой простой функцией стрелки: f = (a, b) => void 0; на getParameters(f)яTypeError: Cannot read property '1' of null
AT

@AT Я только что обновил ответ, чтобы исправить поддержку вашей проблемы
Jaketr00

Спасибо ... но имейте в виду , что круглые скобки больше не требуется, так что вы можете делать такие вещи getParameters(a => b => c => d => a*b*c*d), которые с кодом все еще дает , что TypeError: Cannot read property '1' of null... в то время как это работает stackoverflow.com/a/29123804
AT

Не работает, когда функция имеет значения по умолчанию (role, name = "bob") Извлеченный параметр name = "bob" вместо ожидаемого "name"
Дмитрий


7

Вы также можете использовать парсер "esprima", чтобы избежать многих проблем с комментариями, пробелами и другими вещами в списке параметров.

function getParameters(yourFunction) {
    var i,
        // safetyValve is necessary, because sole "function () {...}"
        // is not a valid syntax
        parsed = esprima.parse("safetyValve = " + yourFunction.toString()),
        params = parsed.body[0].expression.right.params,
        ret = [];

    for (i = 0; i < params.length; i += 1) {
        // Handle default params. Exe: function defaults(a = 0,b = 2,c = 3){}
        if (params[i].type == 'AssignmentPattern') {
            ret.push(params[i].left.name)
        } else {
            ret.push(params[i].name);
        }
    }

    return ret;
}

Это работает даже с таким кодом:

getParameters(function (hello /*, foo ),* /bar* { */,world) {}); // ["hello", "world"]

6

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

//define like
function test(args) {
    for(var item in args) {
        alert(item);
        alert(args[item]);
    }
}

//then used like
test({
    name:"Joe",
    age:40,
    admin:bool
});

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

2
Этот ответ не является ответом на первоначальный вопрос, который был определен точно. Это показывает решение для совершенно другой проблемы. Оригинальный вопрос относится к технике AngularJS, используемой для внедрения зависимостей. Имена аргументов имеют смысл, поскольку они соответствуют зависимостям модуля, которые DI автоматически предоставляет.
Lambder

4

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

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

<!DOCTYPE html>

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

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

        return lambda(named);
    };
};

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

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

</body>
</html>

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


4

Правильный способ сделать это - использовать анализатор JS. Вот пример использования желудя .

const acorn = require('acorn');    

function f(a, b, c) {
   // ...
}

const argNames = acorn.parse(f).body[0].params.map(x => x.name);
console.log(argNames);  // Output: [ 'a', 'b', 'c' ]

Код здесь находит имена трех (формальных) параметров функции f. Это делается путем подачи fв acorn.parse().


как насчет ценностей?
Эран Отзап

2

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

alert(doSomething.length);

function gotcha (a, b = false, c) {}; alert(gotcha.length)
Балуптон

2

Получив ответ от @ jack-allan, я немного изменил функцию, чтобы включить свойства ES6 по умолчанию, такие как:

function( a, b = 1, c ){};

чтобы все еще вернуться [ 'a', 'b' ]

/**
 * Get the keys of the paramaters of a function.
 *
 * @param {function} method  Function to get parameter keys for
 * @return {array}
 */
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
function getFunctionParameters ( func ) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '');
    var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
    var result = argsList.match( ARGUMENT_NAMES );

    if(result === null) {
        return [];
    }
    else {
        var stripped = [];
        for ( var i = 0; i < result.length; i++  ) {
            stripped.push( result[i].replace(/[\s,]/g, '') );
        }
        return stripped;
    }
}

1
Спасибо, ваш единственный в этой теме, который работал для меня.
AT

1

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

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

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

name.arguments;

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


4
А где имена параметров функций?
Андрей

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

1
//See this:


// global var, naming bB
var bB = 5;

//  Dependency Injection cokntroller
var a = function(str, fn) {
  //stringify function body
  var fnStr = fn.toString();

  // Key: get form args to string
  var args = fnStr.match(/function\s*\((.*?)\)/);
  // 
  console.log(args);
  // if the form arg is 'bB', then exec it, otherwise, do nothing
  for (var i = 0; i < args.length; i++) {
    if(args[i] == 'bB') {
      fn(bB);
    }
  }
}
// will do nothing
a('sdfdfdfs,', function(some){
alert(some)
});
// will alert 5

a('sdfdsdsfdfsdfdsf,', function(bB){
alert(bB)
});

// see, this shows you how to get function args in string

1

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

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

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

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

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

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

    // implement the actual function here
}

doSomething(1, 2, 3, 4)

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

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

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


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

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

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

    return argsObj;
}

1

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

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

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

Я потратил некоторое время на это вчера, взламывая правильный RegExp для решения этой проблемы, и это то, что я придумал. Это работает очень хорошо, и я очень доволен результатом:

const REGEX_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
const REGEX_FUNCTION_PARAMS = /(?:\s*(?:function\s*[^(]*)?\s*)((?:[^'"]|(?:(?:(['"])(?:(?:.*?[^\\]\2)|\2))))*?)\s*(?=(?:=>)|{)/m
const REGEX_PARAMETERS_VALUES = /\s*(\w+)\s*(?:=\s*((?:(?:(['"])(?:\3|(?:.*?[^\\]\3)))((\s*\+\s*)(?:(?:(['"])(?:\6|(?:.*?[^\\]\6)))|(?:[\w$]*)))*)|.*?))?\s*(?:,|$)/gm

/**
 * Retrieve a function's parameter names and default values
 * Notes:
 *  - parameters with default values will not show up in transpiler code (Babel) because the parameter is removed from the function.
 *  - does NOT support inline arrow functions as default values
 *      to clarify: ( name = "string", add = defaultAddFunction )   - is ok
 *                  ( name = "string", add = ( a )=> a + 1 )        - is NOT ok
 *  - does NOT support default string value that are appended with a non-standard ( word characters or $ ) variable name
 *      to clarify: ( name = "string" + b )         - is ok
 *                  ( name = "string" + $b )        - is ok
 *                  ( name = "string" + b + "!" )   - is ok
 *                  ( name = "string" + λ )         - is NOT ok
 * @param {function} func
 * @returns {Array} - An array of the given function's parameter [key, default value] pairs.
 */
function getParams(func) {

  let functionAsString = func.toString()
  let params = []
  let match
  functionAsString = functionAsString.replace(REGEX_COMMENTS, '')
  functionAsString = functionAsString.match(REGEX_FUNCTION_PARAMS)[1]
  if (functionAsString.charAt(0) === '(') functionAsString = functionAsString.slice(1, -1)
  while (match = REGEX_PARAMETERS_VALUES.exec(functionAsString)) params.push([match[1], match[2]])
  return params

}



// Lets run some tests!

var defaultName = 'some name'

function test1(param1, param2, param3) { return (param1) => param1 + param2 + param3 }
function test2(param1, param2 = 4 * (5 / 3), param3) {}
function test3(param1, param2 = "/root/" + defaultName + ".jpeg", param3) {}
function test4(param1, param2 = (a) => a + 1) {}

console.log(getParams(test1)) 
console.log(getParams(test2))
console.log(getParams(test3))
console.log(getParams(test4))

// [ [ 'param1', undefined ], [ 'param2', undefined ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '4 * (5 / 3)' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '"/root/" + defaultName + ".jpeg"' ], [ 'param3', undefined ] ]
// [ [ 'param1', undefined ], [ 'param2', '( a' ] ]
// --> This last one fails because of the inlined arrow function!


var arrowTest1 = (a = 1) => a + 4
var arrowTest2 = a => b => a + b
var arrowTest3 = (param1 = "/" + defaultName) => { return param1 + '...' }
var arrowTest4 = (param1 = "/" + defaultName, param2 = 4, param3 = null) => { () => param3 ? param3 : param2 }

console.log(getParams(arrowTest1))
console.log(getParams(arrowTest2))
console.log(getParams(arrowTest3))
console.log(getParams(arrowTest4))

// [ [ 'a', '1' ] ]
// [ [ 'a', undefined ] ]
// [ [ 'param1', '"/" + defaultName' ] ]
// [ [ 'param1', '"/" + defaultName' ], [ 'param2', '4' ], [ 'param3', 'null' ] ]


console.log(getParams((param1) => param1 + 1))
console.log(getParams((param1 = 'default') => { return param1 + '.jpeg' }))

// [ [ 'param1', undefined ] ]
// [ [ 'param1', '\'default\'' ] ]

Как вы можете сказать, некоторые из имен параметров исчезают, потому что конвейер Babel удаляет их из функции. Если вы запустите это в последнем NodeJS, он будет работать как положено (закомментированные результаты получены от NodeJS).

Другое замечание, как указано в комментарии, заключается в том, что он не работает с встроенными функциями стрелок в качестве значения по умолчанию. Это просто усложняет извлечение значений с помощью RegExp.

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


1

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

function test(arg1,arg2){
    var funcStr = test.toString()
    var leftIndex = funcStr.indexOf('(');
    var rightIndex = funcStr.indexOf(')');
    var paramStr = funcStr.substr(leftIndex+1,rightIndex-leftIndex-1);
    var params = paramStr.split(',');
    for(param of params){
        console.log(param);   // arg1,arg2
    }
}

test();

Этот пример просто чтобы получить имя параметров для вас.
Мичжоу

Этот ответ полезен, но он не отвечает на вопрос. Я проголосовал, потому что это решило мою проблему, но я думаю, что это должно быть перемещено куда-то более подходящее. Возможно поискать похожие вопросы?
Чарви

1

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

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


1

Я изменил версию, взятую из AngularJS, которая реализует механизм внедрения зависимостей для работы без Angular. Я также обновил STRIP_COMMENTSрегулярное выражение для работы с ним ECMA6, поэтому он поддерживает такие вещи, как значения по умолчанию в подписи.

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

function annotate(fn) {
  var $inject,
    fnText,
    argDecl,
    last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
        arg.replace(FN_ARG, function(all, underscore, name) {
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else {
    throw Error("not a function")
  }
  return $inject;
}

console.log("function(a, b)",annotate(function(a, b) {
  console.log(a, b, c, d)
}))
console.log("function(a, b = 0, /*c,*/ d)",annotate(function(a, b = 0, /*c,*/ d) {
  console.log(a, b, c, d)
}))
annotate({})


0

Вы можете получить доступ к значениям аргумента, переданным функции, используя свойство arguments.

    function doSomething()
    {
        var args = doSomething.arguments;
        var numArgs = args.length;
        for(var i = 0 ; i < numArgs ; i++)
        {
            console.log("arg " + (i+1) + " = " + args[i]);  
                    //console.log works with firefox + firebug
                    // you can use an alert to check in other browsers
        }
    }

    doSomething(1, '2', {A:2}, [1,2,3]);    

Разве аргументы не осуждаются? См. Предложение Ionut G. Stan выше.
vikasde

Викасде прав. Доступ к argumentsсвойству экземпляра функции устарел. См developer.mozilla.org/en/Core_JavaScript_1.5_Reference/...
Ionuţ G. Stan

0

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

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

https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments/callee

Все они собираются вызвать toString и заменить на re, чтобы мы могли создать помощника:

// getting names of declared parameters
var getFunctionParams = function (func) {
    return String(func).replace(/[^\(]+\(([^\)]*)\).*/m, '$1');
}

Некоторые примеры:

// Solution 1. deprecated! don't use it!
var myPrivateFunction = function SomeFuncName (foo, bar, buz) {
    console.log(getFunctionParams(arguments.callee));
};
myPrivateFunction (1, 2);

// Solution 2.
var myFunction = function someFunc (foo, bar, buz) {
    // some code
};
var params = getFunctionParams(myFunction);
console.log(params);

// Solution 3.
var cls = function SuperKewlClass (foo, bar, buz) {
    // some code
};
var inst = new cls();
var params = getFunctionParams(inst.constructor);
console.log(params);

Наслаждайтесь с JS!

UPD: Джеку Аллану было предоставлено немного лучшее решение. GJ Джек!


Это может быть еще более простым, если вы используете SomeFuncNameвместо arguments.callee(оба указывают на сам объект функции).
Рафаэль Швейкерт

0

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

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

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

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

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

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


0

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

Это довольно длинный блок кода, так как это вставка включает мини-тестовую среду.

    function do_tests(func) {

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

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

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

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

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

    var out = "";

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

             return false;
         };

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

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

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

    return out + src;
}

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

function_args.tests = [

     function test1 () {

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

            }


        var data = function_args(strip_comments_tester);

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

    }

];
do_tests(function_args);

0

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

// Utility function to extract arg name-value pairs
function getArgs(args) {
    var argsObj = {};

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

    while (tokens = argRe.exec(argList)) {
        argsObj[tokens[1]] = args[argCnt++];
    }

    return argsObj;
}

// Test subject
function add(number1, number2) {
    var args = getArgs(arguments);
    console.log(args); // ({ number1: 3, number2: 4 })
}

// Invoke test subject
add(3, 4);

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


2
Цикл while приводит к бесконечному циклу с предоставленным кодом (по состоянию на 20171121at1047EDT)
George 2.0 Hope

@ George2.0Hope Спасибо за указание на это. Я обновлю ответ.
Атес Горал

args.toSource не является функцией (строка 20) ... но даже если вы измените ее на: console.log (args.toString ()); ... вы получаете ... [объект объекта] ... лучше, если вы это сделаете: console.log (JSON.stringify (args));
Джордж 2.0 Надежда

С 2009 года все немного изменилось!
Атес Горал

1
в случае, если кто-то появится здесь для React, эта функция, которая является удивительной, не будет работать в строгом режиме.
Индивидуальный11

0

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

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

-1

Параметр функции строковое значение изображения динамически из JSON . Поскольку item.product_image2 является строкой URL, вам нужно поместить ее в кавычки, когда вы вызываете changeImage внутри параметра.

Моя функция Onclick

items+='<img src='+item.product_image1+' id="saleDetailDivGetImg">';
items+="<img src="+item.product_image2+"  onclick='changeImage(\""+item.product_image2+"\");'>";

Моя функция

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.