351 Stimmen

Wie kann man die Namen/Werte von Funktionsparametern dynamisch ermitteln?

Gibt es eine Möglichkeit, die Namen der Funktionsparameter einer Funktion dynamisch abzurufen?

Nehmen wir an, meine Funktion sieht wie folgt aus:

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

Nun, wie würde ich eine Liste der Parameternamen und ihre Werte in ein Array von innerhalb der Funktion erhalten?

21voto

Domino Punkte 5551

Ich weiß, dies ist eine alte Frage, aber Anfänger haben Lösungen kopiert, die Parameternamen aus der Zeichenkettendarstellung einer Funktion extrahieren, als ob dies in jedem Code eine gute Praxis wäre. Meistens wird dadurch nur ein Fehler in der Logik versteckt.

Das Schreiben von Parameternamen in den Klammern einer Funktionsdeklaration kann als eine Kurzsyntax für die Erstellung von Variablen angesehen werden. Dies:

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

...ist so ähnlich wie das hier:

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

    console.log("does something");
}

Die Variablen selbst werden im Geltungsbereich der Funktion gespeichert, nicht als Eigenschaften in einem Objekt. So wie man den Namen einer Variablen nicht mit Code manipulieren kann, gibt es auch keine Möglichkeit, den Namen eines Parameters abzurufen, da es sich nicht um eine Zeichenkette handelt, und er könnte bei der JIT-Kompilierung eliminiert werden.

Ich habe die Zeichenkettendarstellung einer Funktion immer als ein Werkzeug für Debugging-Zwecke betrachtet, vor allem wegen der folgenden Tatsache arguments Array-ähnliches Objekt. Es ist nicht erforderlich, den Argumenten überhaupt Namen zu geben. Wenn Sie versuchen, eine stringifizierte Funktion zu parsen, werden Sie nicht über zusätzliche unbenannte Parameter informiert, die sie annehmen könnte.

Hier ist eine noch schlimmere und häufigere Situation. Wenn eine Funktion mehr als 3 oder 4 Argumente hat, könnte es logisch sein, ihr stattdessen ein Objekt zu übergeben, das einfacher zu handhaben ist.

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

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

In diesem Fall ist die Funktion selbst in der Lage, das empfangene Objekt zu lesen und nach seinen Eigenschaften zu suchen und sowohl deren Namen als auch Werte zu erhalten, aber der Versuch, die String-Darstellung der Funktion zu parsen, würde Ihnen nur "obj" als Parameter liefern, was überhaupt nicht nützlich ist.

19voto

ZomoXYZ Punkte 1555

Ich habe die meisten Antworten hier gelesen und möchte meinen Einzeiler hinzufügen.

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

oder

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

oder für eine Einzeiler-Funktion in ECMA6

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

__

Angenommen, Sie haben eine Funktion

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

Der folgende Code liefert "abc,def,ghi,jkl"

Dieser Code funktioniert auch mit der Einrichtung einer Funktion, die Camilo Martin gab:

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

Auch mit Buberssons Kommentar zu Jack Allans Antwort :

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

__

Erläuterung

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

Dies schafft eine Regulärer Ausdruck con el new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)') . Ich muss verwenden new RegExp weil ich eine Variable injiziere ( Function.name , den Namen der anvisierten Funktion) in die RegExp.

Ejemplo Wenn der Funktionsname "foo" lautet ( function foo() ), wird die RegExp sein /foo\s*\((.*?)\)/ .

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

Anschließend wird die gesamte Funktion in eine Zeichenkette umgewandelt und alle Zeilenumbrüche werden entfernt. Das Entfernen von Zeilenumbrüchen hilft bei der Einrichtung der Funktion Camilo Martin gab.

.exec(...)[1]

というものです。 RegExp.prototype.exec Funktion. Sie entspricht im Wesentlichen dem Regulären Exponenten ( new RegExp() ) in den String ( Function.toString() ). Dann wird die [1] gibt die erste Erfassungsgruppe die im Regulären Exponenten ( (.*?) ).

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

Dadurch wird jeder Kommentar innerhalb von /* y */ , und entfernen Sie alle Leerzeichen.


Dies unterstützt nun auch das Lesen und Verstehen von Pfeilen ( => ) Funktionen, wie z.B. f = (a, b) => void 0; , in dem Function.toString() würde zurückkehren (a, b) => void 0 anstelle der normalen Funktion der function f(a, b) { return void 0; } . Der ursprüngliche reguläre Ausdruck hätte in seiner Verwirrung einen Fehler ausgelöst, aber das ist jetzt berücksichtigt worden.

Der Wechsel erfolgte von new RegExp(Function.name+'\\s*\\((.*?)\\)') ( /Function\s*\((.*?)\)/ ) an new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)') ( /(?:Function\s*|^)\((.*?)\)/ )


Wenn Sie alle Parameter in ein Array statt in einen durch Kommas getrennten String verwandeln wollen, fügen Sie am Ende einfach .split(',') .

14voto

Zack Morris Punkte 4504

Da es sich bei JavaScript um eine Skriptsprache handelt, sollte die Introspektion meines Erachtens die Ermittlung von Funktionsparameternamen unterstützen. Der Verzicht auf diese Funktionalität ist ein Verstoß gegen die ersten Prinzipien, also habe ich beschlossen, das Problem weiter zu untersuchen.

Das führte mich zu diese Frage aber keine eingebauten Lösungen. Das führte mich zu diese Antwort die erklärt, dass arguments ist nur veraltet außerhalb die Funktion, also können wir nicht mehr myFunction.arguments oder wir bekommen:

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

Zeit, die Ärmel hochzukrempeln und an die Arbeit zu gehen:

Das Abrufen von Funktionsparametern erfordert einen Parser, da komplexe Ausdrücke wie 4*(5/3) können als Standardwerte verwendet werden. Also Gaafar's Antwort o James Drews Antwort sind bis jetzt die besten Ansätze.

Ich habe die babylon y esprima Parser, aber leider können sie keine eigenständigen anonymen Funktionen parsen, wie es in Antwort von Mateusz Charytoniuk . Ich habe jedoch eine andere Lösung gefunden, indem ich den Code in Klammern gesetzt habe, um die Logik nicht zu verändern:

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

Die Zeilenumbrüche verhindern Probleme mit // (einzeilige Kommentare).

Wenn kein Parser zur Verfügung steht, ist die nächstbeste Option die Verwendung einer bewährten Technik wie die regulären Ausdrücke des Dependency Injectors von Angular.js. Ich habe eine funktionale Version von Antwort von Lambder con humbletim's Antwort und fügte eine optionale ARROW Boolescher Wert zur Kontrolle, ob ES6-Fettpfeilfunktionen von den regulären Ausdrücken zugelassen werden.


Hier sind zwei Lösungen, die ich zusammengestellt habe. Beachten Sie, dass diese keine Logik haben, um zu erkennen, ob eine Funktion eine gültige Syntax hat, sie extrahieren nur die Argumente. Das ist im Allgemeinen in Ordnung, da wir normalerweise geparste Funktionen an getArguments() ihre Syntax ist also bereits gültig.

Ich werde versuchen, diese Lösungen so gut wie möglich zu kuratieren, aber ohne die Bemühungen der JavaScript-Maintainer wird dies ein offenes Problem bleiben.

Node.js-Version (nicht lauffähig, bis StackOverflow Node.js unterstützt):

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() {}');

Vollständiges Arbeitsbeispiel:

https://repl.it/repls/SandybrownPhonyAngles

Browserversion (beachten Sie, dass sie beim ersten komplexen Standardwert stehen bleibt):

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() {}');

Vollständiges Arbeitsbeispiel:

https://repl.it/repls/StupendousShowyOffices

10voto

Itay Maman Punkte 29121

Der richtige Weg, dies zu tun, ist die Verwendung eines JS-Parsers. Hier ist ein Beispiel mit Eichel .

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

Der folgende Code ermittelt die Namen der drei (formalen) Parameter der Funktion f . Er tut dies durch Fütterung f in acorn.parse() .

10voto

Sie können auch den Parser "esprima" verwenden, um viele Probleme mit Kommentaren, Leerzeichen und anderen Dingen innerhalb der Parameterliste zu vermeiden.

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;
}

Es funktioniert sogar mit Code wie diesem:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X