Як динамічно отримувати імена / значення параметрів функції?


300

Чи є спосіб отримати ім’я параметрів функції динамічно?

Скажімо, моя функція виглядає так:

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

Тепер, як я можу отримати список імен параметрів та їх значень у масив зсередини функції?


Дякую всім. Оглянувши, я знайшов рішення на SO: stackoverflow.com/questions/914968/… Він використовує регулярний вираз для отримання назви парама. Це, мабуть, не найкраще рішення, проте для мене це працює.
vikasde

8
давайте вперед і позначте це як відповів приятель. нових відповідей не приходить.
Меттью Грейвс

function doSomething(...args) { /*use args*/}
кают

Відповіді:


322

Наступна функція поверне масив імен параметрів будь-якої переданої функції.

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

Приклад використання:

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

Редагувати :

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

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

Я кажу, що в більшості випадків є деякі речі, які сприятимуть цьому

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

Редагувати : Я також зауважу, що vikasde також хоче значення параметрів у масиві. Це вже передбачено в локальній змінній з назвою аргументів.

витяг з https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments :

Об'єктом аргументів не є масив. Він схожий на масив, але не має властивостей масиву, крім довжини. Наприклад, у нього немає методу pop. Однак його можна перетворити на реальний масив:

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

Якщо доступні дженерики Array, замість них можна використовувати наступне:

var args = Array.slice(arguments);

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

1
Я змінив функцію повернення порожнього масиву (замість null), коли немає жодних аргументів
BT

2
Складання регулярного виразів є витратом, тому ви хочете уникати компіляції складних регулярних виразів не один раз. Ось чому це робиться поза функцією
Джек Аллан

2
КОРЕКЦІЯ: збирався змінити регулярний вираз з модифікатором / s, який perl дозволяє так "." може також відповідати новій лінії. Це необхідно для багаторядкових коментарів всередині / * * /. Виявляється, Jage regex не дозволяє модифікатор / s. Оригінальний регулярний вираз із використанням [/ s / S] відповідає новим символам рядка. СООО, проігноруйте попередній коментар.
tgoneil

1
@andes Зауважте, що ви включаєте компіляцію регулярних виразів у свої тести. Це потрібно зробити лише один раз. Ваші результати можуть бути різними, якщо ви перемістите компіляцію регулярних виразів на етап налаштування замість тесту
Джек Аллан

123

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

А ось пояснення цього взято з http://docs.angularjs.org/tutorial/step_05

Інжектор залежностей кутових надає послуги вашому контролеру під час створення контролера. Інжектор залежностей також піклується про створення будь-яких перехідних залежностей, які може мати служба (послуги часто залежать від інших служб).

Зауважте, що назви аргументів є вагомими, оскільки інжектор використовує їх для пошуку залежностей.

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

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

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

40
@apaidnerd з кров'ю демонів і нерестом сатани. Регекс ?! Було б здорово, якби в JS був побудований спосіб, чи не так.
Депутат Адітія

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)
Майкл Одеррер

1
Це також не буде працювати, якщо є значення рядкових значень за замовчуванням, які містять "//" або "/ *"
skerit

23

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

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

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

["hi", "you"]

1
@AlexMills Одне, що я помітив, - це те, що Spec for Arrow Functions говорить про те, що вони не повинні розглядатися як "функції". Це означає, що це не було б доречним для відповідності функцій масиву. "Це" не встановлено однаково, і вони також не повинні використовуватися як функції. Це було те, що я навчився важким шляхом. ($ myService) => $ myService.doSomething () виглядає круто, але це неправильне використання функцій масиву.
Ендрю Т Фіннелл

20

У багатьох відповідях тут використовуються регулярні вирази, це добре, але він не надто добре обробляє нові доповнення до мови (наприклад, функції стрілок та класи). Також слід зауважити, що якщо ви використовуєте будь-яку з цих функцій для мінімізованого коду, вона піде 🔥. Він буде використовувати будь-яку мінімізовану назву. Кутовий обходить це, дозволяючи передати впорядкований масив рядків, який відповідає порядку аргументів при реєстрації їх у контейнері 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": {}
        }
    }
}

Його з'єднали всю програму. Найкраще, що додаток легко перевірити (ви можете просто інстанціювати кожен клас і передавати макети / заглушки / тощо). Також якщо вам потрібно поміняти місцями реалізації, ви можете зробити це з одного місця. Все це можливо через об’єкти 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 (це бібліотека, яка приймає функцію, яка зазвичай приймає зворотний дзвінок, а потім перетворює його на Promise). Вони використовують це для пошуку загальних назв для зворотних викликів (наприклад, cb / callback / тощо), а потім вони можуть перевірити, чи функція асинхронізується чи синхронізується перед тим, як завершити її.
Джеймс Дрю

Дивіться цей файл для їх розбору . Це трохи наївно, але це стосується більшості випадків.
Джеймс Дрю

Цікаво, я дивуюсь, що така бібліотека привернула будь-яку увагу. Ну, є багато відкритих питань щодо проблемних ситуацій, таких, як я описав. Як я вже сказав, якщо це для налагодження, це добре, але покладатися на строкове перетворення функцій у виробничих середовищах занадто ризиковано.
Доміно

На "смішному" стороні ви можете змусити всі функції діяти так, ніби вони були анонімними вбудованими, виконавши це:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Доміно

1
Погодьтеся, це трохи безладно впоратися з цим. Найкраще і найпростіше використання - це ін'єкція залежності. У цьому випадку ви володієте кодом і можете обробляти іменування та інші області коду. Я думаю, що саме тут було б найбільше використання. Наразі я використовую esprima (замість регулярного вираження) та ES6 Proxy( конструктор, пастка та застосувати пастку ) та 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

Час закатати рукави та приступити до роботи:

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

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

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

Нові рядки запобігають проблемам із // (однорядкові коментарі).

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


Ось два рішення, які я зібрав разом. Зауважте, що вони не мають логіки, щоб визначити, чи має функція дійсний синтаксис, вони витягують лише аргументи. Це, як правило, нормально, оскільки ми зазвичай передаємо розібрані функції, щоб getArguments()їх синтаксис вже був дійсним.

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

Версія Node.js (не запускається, поки StackOverflow не підтримує Node.js):

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

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

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

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

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

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

	object[func] = getArguments(func);

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

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

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

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

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

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

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

Повний робочий приклад:

https://repl.it/repls/SandybrownPhonyAngles

Версія браузера (зауважте, що вона зупиняється на першому комплексному значенні за замовчуванням):

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

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

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

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

	object[func] = getArguments(func);

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

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

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

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

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

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

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

Повний робочий приклад:

https://repl.it/repls/StupendousShowyOffices


якщо я не помиляюсь, у вашій версії браузера перший умовний FUNC_ARGS буде працювати як зі стрілкою, так і з традиційними функціями, тому друга частина вам не потрібна, і ви можете позбутися залежності від СТРЕЛИ.
pwilcox

Це чудово! Я шукав таке рішення, яке використовує аналізатор для покриття синтаксису ES6. Я планую використовувати це для створення веселого відповідника "реалізує інтерфейс", оскільки просто за допомогою функції 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()) в String ( Function.toString()). Тоді [1]заповіт поверне першу групу захоплення, знайдену в регулярному експоненті ( (.*?)).

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

Це видалить кожен коментар усередині /*та */та видалить усі пробіли.


Це також підтримує функції читання та розуміння стрілок ( =>), таких як f = (a, b) => void 0;, у яких Function.toString()повертається (a, b) => void 0замість звичайної функції function f(a, b) { return void 0; }. Початковий регулярний вираз призвів до помилки в його плутанині, але зараз це враховується.

Зміна була від new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) до new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Якщо ви хочете зробити всі параметри в масиві замість рядка, розділеного комами, в кінці просто додайте .split(',').


доволі приємний. один рядок, обробляє функції зі стрілками, не має зовнішніх залежностей і охоплює більш ніж достатньо крайових випадків, принаймні для мого призначення. якщо ви можете зробити обґрунтовані припущення щодо функцій підписів, ви будете мати справу з цим - все, що вам знадобиться. Дякую!
Чарлі Робертс

Не працює з цією простою стрілочною функцією 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

Не працює, коли функція має значення за замовчуванням (роль, ім'я = "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 автоматично надає.
Ламбдер

4

Я не знаю, чи підходить це рішення вашій проблемі, але воно дозволяє переглядати будь-яку функцію, яку ви хочете, не змінюючи код, який її використовує. Існуючі виклики використовуватимуть позиціоновані парами, тоді як реалізація функції може використовувати "названі парами" (один хеш-парам).

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

<!DOCTYPE html>

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

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

        return lambda(named);
    };
};

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

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

</body>
</html>

Сподіваюся, це допомагає.


4

Правильний спосіб зробити це - використовувати JS-аналізатор. Ось приклад використання жолудя .

const acorn = require('acorn');    

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

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

Код тут знаходить назви трьох (формальних) параметрів функції f. Це робиться шляхом введення fв їжу acorn.parse().


як щодо значень?
eran otzap

2

Я не знаю, як отримати список параметрів, але ви можете зробити це, щоб отримати скільки очікує.

alert(doSomething.length);

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

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...?');. Аргументи функції - це об’єкт, схожий на масив, який має безліч індексів. Я не думаю, що хеш-картування можливе, але ви можете просто пройти Об'єкт-літерал, що є хорошою практикою, якщо у вас все-таки більше 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 масиви разом в 1 масив, який має ім'я та значення всіх параметрів.

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

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

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

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

    // implement the actual function here
}

doSomething(1, 2, 3, 4)

і зареєстрований об'єкт буде

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

А ось робочий приклад https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a


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

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

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

    return argsObj;
}

1

Нічого собі так багато відповідей .. Я впевнений, що це закопується. Навіть тому я вважав, що це може бути корисним для деяких.

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

Ця функція дуже корисна для цілей налагодження, наприклад: ведення журналу, що називається функцією, за допомогою її парам, значень парам і аргументів за замовчуванням.

Я витратив деякий час на це вчора, зламавши право RegExp, щоб вирішити цю проблему, і ось що я придумав. Це працює дуже добре, і я дуже задоволений результатом:

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

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

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

}



// Lets run some tests!

var defaultName = 'some name'

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

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

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


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

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

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


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

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

Як ви можете сказати, деякі назви параметрів зникають, оскільки транспілятор Бабеля видаляє їх із функції. Якщо ви запустили це в останньому 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();

Цей приклад лише для отримання імені параметрів для вас.
мічжоу

Ця відповідь корисна, але не відповідає на питання. Я проголосував за те, що це вирішило мою проблему, але я вважаю, що її слід перенести кудись більш доцільно. Можливо, шукайте відповідні запитання?
chharvey

1

Цей пакет використовує переробку для створення 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

Ви можете отримати доступ до значень аргументів, переданих функції, за допомогою властивості "аргументи".

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

Чи не застарілі аргументи? Дивіться пропозицію Іонута Г. Стана вище.
vikasde

vikasde має рацію. Доступ до argumentsвластивості функціонального примірника застарілий. Дивіться developer.mozilla.org/uk/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan

0

Це досить просто.

Спочатку є застаріле arguments.callee- посилання на називану функцію. По-друге, якщо у вас є посилання на вашу функцію, ви можете легко отримати їх текстове зображення. По-третє, якщо ви викликаєте свою функцію конструктора, ви також можете мати посилання через yourObject.constructor. Примітка. Перше рішення застаріле, тому якщо ви не можете його використовувати, ви також повинні подумати про свою архітектуру додатків. Якщо точні імена змінних вам не потрібні, просто використовуйте внутрішню змінну функціїarguments без будь-якої магії.

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

Усі вони зателефонують доString та замінять повторно, щоб ми могли створити помічника:

// 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: Джеку Аллану насправді було запропоновано трохи краще рішення. Джей Джек!


Це може бути ще простішим, якщо ви використовуєте SomeFuncNameзамість arguments.callee(обидва вказують на сам об’єкт функції).
Рафаель Швайкерт

0

Яке б не було рішення, воно не повинно порушувати функції wierd, чий toString()вигляд виглядає так само, як і wierd:

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)
Джордж 2.0 Надія

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

args.toSource не є функцією (рядок 20) ... але навіть якщо ви зміните її на: console.log (args.toString ()); ... ви отримаєте ... [object Object] ... краще, якщо ви зробите: 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.