関数の関数パラメーター名を動的に取得する方法はありますか?
私の機能は次のように見えるとしましょう:
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']
Edit:vikasdeは配列内のパラメーター値も必要としていることにも注意してください。これは、argumentsという名前のローカル変数で既に提供されています。
Argumentsオブジェクトは配列ではありません。配列に似ていますが、長さ以外の配列プロパティはありません。たとえば、popメソッドはありません。ただし、実際の配列に変換できます。
var args = Array.prototype.slice.call(arguments);
配列ジェネリックが使用可能な場合、代わりに次を使用できます。
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;
}
上記の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>
スペースやコメントが発生しやすいエラーの解決策は次のとおりです。
var fn = function(/* whoa) */ hi, you){};
fn.toString()
.replace(/((\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s))/mg,'')
.match(/^function\s*[^\(]*\(\s*([^\)]*)\)/m)[1]
.split(/,/)
["hi", "you"]
ここでの回答の多くは正規表現を使用しています。これは問題ありませんが、言語への新しい追加(矢印関数やクラスなど)をうまく処理しません。また、これらの関数のいずれかを縮小されたコードで使用すると、次の処理に進むことに注意してください。縮小された名前が使用されます。 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プロキシオブジェクトのために可能です。
注:本番環境で使用する準備が整う前にこれを行う必要のある作業がたくさんありますが、どのように見えるかはわかります
答えは少し遅れていますが、同じことを考えている人を助けるかもしれません。 ????
私はこれが古い質問であることを知っていますが、初心者はこれがあらゆるコードで良い習慣であるかのようにこれをコピーペーストしています。ほとんどの場合、パラメータ名を使用するために関数の文字列表現を解析する必要があるため、コードのロジックの欠陥が隠されるだけです。
関数のパラメーターは、実際には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」のみが得られます。これはまったく役に立ちません。
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 の機能バージョンと humbletim'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() {}');
完全な実例:
(function(a,b,c){}).toString().replace(/.*\(|\).*/ig,"").split(',')
=> ["a"、 "b"、 "c"]
私はこれを以前に試しましたが、それを成し遂げるための実用的な方法を見つけたことがありません。代わりにオブジェクトを渡してからループしました。
//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
});
また、「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"]
私はここでほとんどの答えを読んだので、ワンライナーを追加したいと思います。
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(',')
を追加します。
この解決策があなたの問題に合っているかどうかはわかりませんが、それを使用するコードを変更することなく、必要な機能を再定義できます。既存の呼び出しは位置付けされたパラメーターを使用しますが、関数の実装では「名前付きパラメーター」(単一のハッシュパラメーター)を使用できます。
とにかく既存の関数定義を変更すると思いますので、あなたが望むものを作るファクトリ関数を持っていないのはなぜですか:
<!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>
それが役に立てば幸い。
パラメーターのリストを取得する方法がわかりませんが、これを実行して、パラメーターの数を取得できます。
alert(doSomething.length);
@ -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;
}
}
このパッケージはリキャストを使用してASTを作成し、パラメーター名をそれらから収集します。これにより、パターンマッチング、デフォルト引数、矢印関数、およびその他のES6機能をサポートできます。
これに対する答えは3つのステップを必要とします:
argValues
と呼びましょう)。関数内でarguments
として使用できるため、これは簡単です。argNames
と呼びましょう)。これはそれほど簡単ではなく、関数の解析が必要です。複雑な正規表現を自分で行い、Edgeのケース(デフォルトのパラメーター、コメントなど)を心配する代わりに、関数をパラメーターの名前を取得できる抽象構文ツリーに解析する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
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({})
私が通常どのようにそれをするか:
function name(arg1, arg2){
var args = arguments; // array: [arg1, arg2]
var objecArgOne = args[0].one;
}
name({one: "1", two: "2"}, "string");
次のような関数名で引数を参照することもできます。
name.arguments;
お役に立てれば!
//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;
}
これを行う適切な方法は、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つの(正式な)パラメーターの名前を検索します。これは、f
をacorn.parse()
に供給することで行います。
すごい多くの答えがすでにあります。それでも、私はこれが一部の人にとって役に立つかもしれないと考えました。
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を使用して値を抽出するのが複雑になります。
これがあなたに役立つかどうか教えてください!フィードバックをお聞かせください!
注:最上位ソリューションでES6パラメーターの構造化を使用する場合は、次の行を追加します。
if (result[0] === '{' && result[result.length - 1 === '}']) result = result.slice(1, -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();
「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]);
とても簡単です。
最初は、廃止されたarguments.callee
—呼び出された関数への参照です。第二に、関数への参照がある場合は、簡単にテキスト表現を取得できます。 3番目では、関数をコンストラクターとして呼び出す場合、yourObject.constructorを介してリンクを設定することもできます。 NB:最初のソリューションは非推奨になったため、使用できない場合はアプリのアーキテクチャについても考慮する必要があります。正確な変数名が必要ない場合は、関数内部変数arguments
内で魔法を使わずに使用してください。
それらはすべて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ジャック!
解決策が何であれ、そのtoString()
が奇妙なように見える奇妙な関数で壊れてはなりません:
function ( A, b
,c ,d
){}
また、なぜ複雑な正規表現を使用するのですか?これは次のように実行できます。
function getArguments(f) {
return f.toString().split(')',1)[0].replace(/\s/g,'').substr(9).split(',');
}
これはすべての関数でどこでも機能し、唯一の正規表現は.split
トリックのために文字列全体を処理することさえしない空白の削除です。
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
をサポートするブラウザーでのみ機能します。
わかりましたので、適切な答えがたくさんある古い質問です。ここでは、空白を削除するという面倒なタスクを除き、正規表現を使用しない私の製品を紹介します。 (「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);