web-dev-qa-db-ja.com

関数パラメーターの名前/値を動的に取得する方法は?

関数の関数パラメーター名を動的に取得する方法はありますか?

私の機能は次のように見えるとしましょう:

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

さて、どのようにして関数内からパラメータ名とその値のリストを配列に取得しますか?

275
vikasde

次の関数は、渡された関数のパラメーター名の配列を返します。

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

Edit:vikasdeは配列内のパラメーター値も必要としていることにも注意してください。これは、argumentsという名前のローカル変数で既に提供されています。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments からの抜粋:

Argumentsオブジェクトは配列ではありません。配列に似ていますが、長さ以外の配列プロパティはありません。たとえば、popメソッドはありません。ただし、実際の配列に変換できます。

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

配列ジェネリックが使用可能な場合、代わりに次を使用できます。

var args = Array.slice(arguments);
301
Jack Allan

以下は、依存性注入メカニズムの手法を使用する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;
}
118
Lambder

上記のEdgeのすべてのケースにコンパクトな方法で対処しようとする更新されたソリューションを次に示します。

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

簡略化されたテスト出力(完全なテストケースを以下に添付):

'function (a,b,c)...' // returns ["a","b","c"]
'function ()...' // returns []
'function named(a, b, c) ...' // returns ["a","b","c"]
'function (a /* = 1 */, b /* = true */) ...' // returns ["a","b"]
'function fprintf(handle, fmt /*, ...*/) ...' // returns ["handle","fmt"]
'function( a, b = 1, c )...' // returns ["a","b","c"]
'function (a=4*(5/3), b) ...' // returns ["a","b"]
'function (a, // single-line comment xjunk) ...' // returns ["a","b"]
'function (a /* fooled you...' // returns ["a","b"]
'function (a /* function() yes */, \n /* no, */b)/* omg! */...' // returns ["a","b"]
'function ( A, b \n,c ,d \n ) \n ...' // returns ["A","b","c","d"]
'function (a,b)...' // returns ["a","b"]
'function $args(func) ...' // returns ["func"]
'null...' // returns ["null"]
'function Object() ...' // returns []
function $args(func) {  
    return (func + '')
      .replace(/[/][/].*$/mg,'') // strip single-line comments
      .replace(/\s+/g, '') // strip white space
      .replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments  
      .split('){', 1)[0].replace(/^[^(]*[(]/, '') // extract the parameters  
      .replace(/=[^,]+/g, '') // strip any ES6 defaults  
      .split(',').filter(Boolean); // split & filter [""]
}  

// test cases  
document.getElementById('console_info').innerHTML = (
[  
  // formatting -- typical  
  function(a,b,c){},  
  function(){},  
  function named(a, b,  c) {  
/* multiline body */  
  },  
    
  // default values -- conventional  
  function(a /* = 1 */, b /* = true */) { a = a||1; b=b||true; },  
  function fprintf(handle, fmt /*, ...*/) { },  
  
  // default values -- ES6  
  "function( a, b = 1, c ){}",  
  "function (a=4*(5/3), b) {}",  
  
  // embedded comments -- sardonic  
  function(a, // single-line comment xjunk) {}
    b //,c,d
  ) // single-line comment
  {},  
  function(a /* fooled you{*/,b){},  
  function /* are you kidding me? (){} */(a /* function() yes */,  
   /* no, */b)/* omg! */{/*}}*/},  
  
  // formatting -- sardonic  
  function  (  A,  b  
,c  ,d  
  )  
  {  
  },  
  
  // by reference  
  this.jQuery || function (a,b){return new e.fn.init(a,b,h)},
  $args,  
  
  // inadvertent non-function values  
  null,  
  Object  
].map(function(f) {
    var abbr = (f + '').replace(/\n/g, '\\n').replace(/\s+|[{]+$/g, ' ').split("{", 1)[0] + "...";
    return "    '" + abbr + "' // returns " + JSON.stringify($args(f));
  }).join("\n") + "\n"); // output for copy and paste as a markdown snippet
<pre id='console_info'></pre>
34
humbletim

スペースやコメントが発生しやすいエラーの解決策は次のとおりです。

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

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

["hi", "you"]
21
bubersson

ここでの回答の多くは正規表現を使用しています。これは問題ありませんが、言語への新しい追加(矢印関数やクラスなど)をうまく処理しません。また、これらの関数のいずれかを縮小されたコードで使用すると、次の処理に進むことに注意してください。縮小された名前が使用されます。 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にargsパーサーを使用したい場合、Proxyを使用してそれを処理する方法のアイデアを提供します。ただし、このアプローチには1つだけ注意点があります。通常のパラメータの代わりに、構造化割り当てを使用する必要があります。インジェクタープロキシを渡すと、構造化はオブジェクトのゲッターを呼び出すのと同じです。

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プロキシオブジェクトのために可能です。

注:本番環境で使用する準備が整う前にこれを行う必要のある作業がたくさんありますが、どのように見えるかはわかります

答えは少し遅れていますが、同じことを考えている人を助けるかもしれません。 ????

16
James Drew

私はこれが古い質問であることを知っていますが、初心者はこれがあらゆるコードで良い習慣であるかのようにこれをコピーペーストしています。ほとんどの場合、パラメータ名を使用するために関数の文字列表現を解析する必要があるため、コードのロジックの欠陥が隠されるだけです。

関数のパラメーターは、実際にはargumentsと呼ばれる配列のようなオブジェクトに格納されます。最初の引数はarguments[0]、2番目は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」のみが得られます。これはまったく役に立ちません。

12
Jacque Goupil

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)のような複雑な式をデフォルト値として使用できるため、関数パラメーターの取得にはパーサーが必要です。 Gaafarの答え または James Drewの答え はこれまでのところ最良のアプローチです。

babylon および esprima パーサーを試しましたが、残念ながら Mateusz Charytoniuk's answer で指摘されているように、スタンドアロンの匿名関数を解析できません。ロジックを変更しないように、コードを括弧で囲むことで別の回避策を見つけました。

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

改行は、//(単一行コメント)に関する問題を防ぎます。

⭐パーサーが利用できない場合、次に最適なオプションは、Angular.jsの依存性インジェクターの正規表現のような実証済みの手法を使用することです。 Lambder's answer の機能バージョンと hum​​bletim's answer を組み合わせ、オプションのARROWブール値を追加して、ES6の太い矢印関数を正規表現で許可するかどうかを制御しました。


以下に、私がまとめた2つのソリューションを示します。これらには、関数に有効な構文があるかどうかを検出するロジックはなく、引数のみを抽出することに注意してください。通常、解析済みの関数を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

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

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

7
Will

私はこれを以前に試しましたが、それを成し遂げるための実用的な方法を見つけたことがありません。代わりにオブジェクトを渡してからループしました。

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

また、「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

私はここでほとんどの答えを読んだので、ワンライナーを追加したいと思います。

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

または

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

またはECMA6の1ライナー機能用

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

__

機能があるとしましょう

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

以下のコードは"abc,def,ghi,jkl"を返します

このコードは、 Camilo Martin が提供する関数のセットアップでも機能します。

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

ジャックアランの答え に関するBuberssonのコメントも:

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

__

説明

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

これにより、new RegExp(Function.name+'\\s*\\((.*?)\\)')正規指数 が作成されます。 RegExpに変数(new RegExp、対象となる関数の名前)を注入しているため、Function.nameを使用する必要があります。

関数名が「foo」(function foo())の場合、RegExpは/foo\s*\((.*?)\)/になります。

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

次に、関数全体を文字列に変換し、すべての改行を削除します。改行を削除すると、関数のセットアップに役立ちます Camilo Martin が与えられました。

.exec(...)[1]

これは RegExp.prototype.exec 関数です。基本的には、正規指数(new RegExp())を文字列(Function.toString())に一致させます。その後、[1]は、最初の Capture Group を正規指数((.*?))で検出します。

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

これにより、/*および*/内のすべてのコメントが削除され、すべてのスペースが削除されます。


すべてのパラメーターを、コンマで区切られたストリングではなく配列にする場合は、最後に.split(',')を追加します。

5
Jaketr00

この解決策があなたの問題に合っているかどうかはわかりませんが、それを使用するコードを変更することなく、必要な機能を再定義できます。既存の呼び出しは位置付けされたパラメーターを使用しますが、関数の実装では「名前付きパラメーター」(単一のハッシュパラメーター)を使用できます。

とにかく既存の関数定義を変更すると思いますので、あなたが望むものを作るファクトリ関数を持っていないのはなぜですか:

<!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
Ionuț G. Stan

パラメーターのリストを取得する方法がわかりませんが、これを実行して、パラメーターの数を取得できます。

alert(doSomething.length);
2
Ólafur Waage

@ -jack-allanから answer を取得して、次のような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;
    }
}
2
thelastshadow

このパッケージはリキャストを使用してASTを作成し、パラメーター名をそれらから収集します。これにより、パターンマッチング、デフォルト引数、矢印関数、およびその他のES6機能をサポートできます。

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

1
user3436035

これに対する答えは3つのステップを必要とします:

  1. 関数に渡される実際のパラメーターの値を取得するには(argValuesと呼びましょう)。関数内でargumentsとして使用できるため、これは簡単です。
  2. 関数シグネチャからパラメーター名を取得するには(argNamesと呼びましょう)。これはそれほど簡単ではなく、関数の解析が必要です。複雑な正規表現を自分で行い、Edgeのケース(デフォルトのパラメーター、コメントなど)を心配する代わりに、関数をパラメーターの名前を取得できる抽象構文ツリーに解析する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
Gaafar

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({})
1
loretoparisi

私が通常どのようにそれをするか:

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

次のような関数名で引数を参照することもできます。

name.arguments;

お役に立てれば!

1
Cody
//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
Paul Lan
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
user3018868

これを行う適切な方法は、JSパーサーを使用することです。 acorn を使用した例を次に示します。

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の3つの(正式な)パラメーターの名前を検索します。これは、facorn.parse()に供給することで行います。

1
Itay Maman

すごい多くの答えがすでにあります。それでも、私はこれが一部の人にとって役に立つかもしれないと考えました。

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
SnailCrusher

注:最上位ソリューションでES6パラメーターの構造化を使用する場合は、次の行を追加します。

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

以下に簡単な例を示します。

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

「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]);    
0
letronje

とても簡単です。

最初は、廃止されたarguments.callee —呼び出された関数への参照です。第二に、関数への参照がある場合は、簡単にテキスト表現を取得できます。 3番目では、関数をコンストラクターとして呼び出す場合、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ジャック!

0

解決策が何であれ、そのtoString()が奇妙なように見える奇妙な関数で壊れてはなりません:

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

screenshot from console

また、なぜ複雑な正規表現を使用するのですか?これは次のように実行できます。

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

これはすべての関数でどこでも機能し、唯一の正規表現は.splitトリックのために文字列全体を処理することさえしない空白の削除です。

0
Camilo Martin

1つの方法を次に示します。

// 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をサポートするブラウザーでのみ機能します。

0
Ateş Göral

わかりましたので、適切な答えがたくさんある古い質問です。ここでは、空白を削除するという面倒なタスクを除き、正規表現を使用しない私の製品を紹介します。 (「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
unsynchronized