関数の関数パラメータ名を動的に取得する方法はありますか?
私の関数が次のようになっているとしましょう:
function doSomething(param1, param2, .... paramN){
// fill an array with the parameter name and value
// some other code
}
では、関数内からパラメーター名とその値のリストを配列に取得するにはどうすればよいでしょうか?
関数の関数パラメータ名を動的に取得する方法はありますか?
私の関数が次のようになっているとしましょう:
function doSomething(param1, param2, .... paramN){
// fill an array with the parameter name and value
// some other code
}
では、関数内からパラメーター名とその値のリストを配列に取得するにはどうすればよいでしょうか?
次の関数は、渡された関数のパラメーター名の配列を返します。
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /([^\s,]+)/g;
function getParamNames(func) {
var fnStr = func.toString().replace(STRIP_COMMENTS, '');
var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES);
if(result === null)
result = [];
return result;
}
使用例:
getParamNames(getParamNames) // returns ['func']
getParamNames(function (a,b,c,d){}) // returns ['a','b','c','d']
getParamNames(function (a,/*b,c,*/d){}) // returns ['a','d']
getParamNames(function (){}) // returns []
編集:
ES6 の発明により、この関数はデフォルトのパラメーターによってトリップされる可能性があります。これは、ほとんどの場合に機能する簡単なハックです。
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;
ほとんどの場合、それをつまずかせるものがあるので、私は言います
function (a=4*(5/3), b) {} // returns ['a']
編集:また、vikasdeが配列内のパラメーター値も必要としていることにも注意してください。これは、arguments という名前のローカル変数で既に提供されています。
引数オブジェクトは配列ではありません。Array に似ていますが、長さ以外の Array プロパティはありません。たとえば、pop メソッドはありません。ただし、実際の配列に変換できます。
var args = Array.prototype.slice.call(arguments);
Array ジェネリックが利用可能な場合、代わりに以下を使用できます。
var args = Array.slice(arguments);
以下は、依存性注入メカニズムにこの手法を使用する 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;
}
スペースやコメントによるエラーが発生しにくいソリューションは次のとおりです。
var fn = function(/* whoa) */ hi, you){};
fn.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
.match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
.split(/,/)
["hi", "you"]
これは古い質問であることは知っていますが、初心者は、これがどのコードでも良い習慣であるかのように、これをコピーペーストしてきました。ほとんどの場合、パラメーター名を使用するために関数の文字列表現を解析しなければならないことは、コードのロジックの欠陥を隠しているだけです。
関数のパラメータは、実際には と呼ばれる配列のようなオブジェクトに格納されます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」しか得られません。これはまったく役に立ちません。
私は以前にこれを試みましたが、それを成し遂げるための実用的な方法を見つけることはありませんでした。代わりにオブジェクトを渡して、それをループすることになりました。
//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
});
このソリューションが問題に適しているかどうかはわかりませんが、それを使用するコードを変更することなく、必要な関数を再定義できます。既存の呼び出しは配置されたパラメーターを使用しますが、関数の実装は「名前付きパラメーター」(単一のハッシュパラメーター)を使用する場合があります。
とにかく既存の関数定義を変更すると思ったので、必要なものだけを作成するファクトリ関数を用意してみませんか。
<!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>
それが役に立てば幸い。
@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;
}
}
パラメータのリストを取得する方法はわかりませんが、これを実行して、期待される数を取得できます。
alert(doSomething.length);
//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
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;
}
これに対する答えには、次の 3 つのステップが必要です。
argValues
)。arguments
関数内で使用できるため、これは簡単です。argNames
)。これは簡単ではなく、関数の解析が必要です。複雑な正規表現を自分で実行し、エッジ ケース (デフォルト パラメーター、コメントなど) を心配する代わりに、babylon のようなライブラリを使用して、関数を抽象構文ツリーに解析し、そこからパラメーターの名前を取得できます。コードはこのようになります
const babylon = require("babylon")
function doSomething(a, b, c) {
// get the values of passed argumenst
const argValues = arguments
// get the names of the arguments by parsing the function
const ast = babylon.parse(doSomething.toString())
const argNames = ast.program.body[0].params.map(node => node.name)
// join the 2 arrays, by looping over the longest of 2 arrays
const maxLen = Math.max(argNames.length, argValues.length)
const args = []
for (i = 0; i < maxLen; i++) {
args.push({name: argNames[i], value: argValues[i]})
}
console.log(args)
// implement the actual function here
}
doSomething(1, 2, 3, 4)
ログに記録されたオブジェクトは
[
{
"name": "a",
"value": 1
},
{
"name": "c",
"value": 3
},
{
"value": 4
}
]
そして、これが実際の例ですhttps://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a
うわー、すでにたくさんの答えがあります..これが埋もれてしまうと確信しています。それでも、これは一部の人にとっては役立つかもしれないと考えました。
ES6ではデフォルト値ではうまく機能しないため、選択した回答に完全には満足していませんでした。また、デフォルト値情報も提供しません。また、外部ライブラリに依存しない軽量な関数も必要でした。
この関数は、デバッグ目的で非常に役立ちます。たとえば、呼び出された関数をパラメーター、デフォルトのパラメーター値、および引数とともにログに記録します。
私は昨日これに時間を費やし、この問題を解決するために適切な正規表現をクラックしました。これが私が思いついたものです。それは非常にうまく機能し、結果に非常に満足しています:
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 を使用して値を抽出することが非常に複雑になります。
これが役に立ったかどうか教えてください!フィードバックをお待ちしております。
私が通常行う方法:
function name(arg1, arg2){
var args = arguments; // array: [arg1, arg2]
var objecArgOne = args[0].one;
}
name({one: "1", two: "2"}, "string");
次のように、関数名で引数を参照することもできます。
name.arguments;
お役に立てれば!
Angular なしで動作するように、依存性注入メカニズムを実装するAngularJSから取得したバージョンを変更しました。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({})
解決策が何であれ、奇妙な関数で壊れてはなりませんtoString()
。
function ( A, b
,c ,d
){}
また、なぜ複雑な正規表現を使用するのでしょうか? これは次のように行うことができます:
function getArguments(f) {
return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}
.split
これはすべての関数でどこでも機能し、唯一の正規表現は、トリックのために文字列全体を処理しない空白の削除です。
「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]);