Wie bekomme ich Funktionsparameternamen / -werte dynamisch?


300

Gibt es eine Möglichkeit, die Funktionsparameternamen einer Funktion dynamisch abzurufen?

Angenommen, meine Funktion sieht folgendermaßen aus:

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

Wie würde ich nun eine Liste der Parameternamen und ihrer Werte in ein Array innerhalb der Funktion einfügen?


Danke an Alle. Nachdem ich mich umgesehen hatte , fand ich die Lösung auf SO: stackoverflow.com/questions/914968/… Es wird ein regulärer Ausdruck verwendet, um den Parameternamen abzurufen. Es ist wahrscheinlich nicht die beste Lösung, aber es funktioniert für mich.
Vikasde

8
Lassen Sie uns fortfahren und dies als beantworteten Kumpel markieren. Es kommen keine neuen Antworten.
Matthew Graves

function doSomething(...args) { /*use args*/}
Caub

Antworten:


322

Die folgende Funktion gibt ein Array der Parameternamen aller übergebenen Funktionen zurück.

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

Anwendungsbeispiel:

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

Bearbeiten :

Mit der Erfindung von ES6 kann diese Funktion durch Standardparameter ausgelöst werden. Hier ist ein kurzer Hack, der in den meisten Fällen funktionieren sollte:

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

Ich sage die meisten Fälle, weil es einige Dinge gibt, die es auslösen werden

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

Bearbeiten : Ich stelle auch fest, dass vikasde die Parameterwerte auch in einem Array haben möchte. Dies ist bereits in einer lokalen Variablen mit dem Namen argument enthalten.

Auszug aus https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions_and_function_scope/arguments :

Das Argumentobjekt ist kein Array. Es ähnelt einem Array, hat jedoch keine Array-Eigenschaften außer der Länge. Zum Beispiel hat es nicht die Pop-Methode. Es kann jedoch in ein echtes Array konvertiert werden:

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

Wenn Array-Generika verfügbar sind, können Sie stattdessen Folgendes verwenden:

var args = Array.slice(arguments);

12
Beachten Sie, dass diese Lösung aufgrund von Kommentaren und Leerzeichen fehlschlagen kann - zum Beispiel: var fn = function(a /* fooled you)*/,b){};führt zu ["a", "/*", "fooled", "you"]
bubersson

1
Ich habe die Funktion so geändert, dass ein leeres Array (anstelle von null) zurückgegeben wird, wenn keine Argumente vorhanden sind
BT

2
Das Kompilieren eines regulären Ausdrucks ist mit Kosten verbunden. Daher möchten Sie vermeiden, dass komplexe reguläre Ausdrucke mehrmals kompiliert werden. Dies ist der Grund, warum es außerhalb der Funktion durchgeführt wird
Jack Allan

2
KORREKTUR: Wollte den regulären Ausdruck mit einem / s-Modifikator ändern, der perl so zulässt '.' kann auch mit Zeilenumbruch übereinstimmen. Dies ist für mehrzeilige Kommentare in / * * / erforderlich. Es stellt sich heraus, dass Javascript Regex den Modifikator / s nicht zulässt. Die ursprüngliche Regex mit [/ s / S] stimmt mit Zeilenumbrüchen überein. SOOO, bitte ignorieren Sie den vorherigen Kommentar.
Tgoneil

1
@andes Beachten Sie, dass Sie die Regex-Kompilierung in Ihre Tests einbeziehen. Diese sollten nur einmal durchgeführt werden. Ihre Ergebnisse können unterschiedlich sein, wenn Sie die Regex-Kompilierung anstelle des Tests in den Setup-Schritt verschieben
Jack Allan

123

Unten finden Sie den Code von AngularJS, der die Technik für seinen Abhängigkeitsinjektionsmechanismus verwendet.

Und hier ist eine Erklärung davon aus http://docs.angularjs.org/tutorial/step_05

Der Abhängigkeitsinjektor von Angular bietet Ihrem Controller Dienste, wenn der Controller erstellt wird. Der Abhängigkeitsinjektor sorgt auch dafür, dass der Dienst möglicherweise transitive Abhängigkeiten aufweist (Dienste hängen häufig von anderen Diensten ab).

Beachten Sie, dass die Namen der Argumente von Bedeutung sind, da der Injektor diese verwendet, um die Abhängigkeiten nachzuschlagen.

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

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

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

40
@apaidnerd mit dem Blut von Dämonen und Laich von Satan, anscheinend. Regex?! Wäre cool, wenn es in JS einen eingebauten Weg gäbe, nicht wahr?
Aditya MP

6
@apaidnerd, so wahr! Ich dachte nur - wie zum Teufel ist das umgesetzt? Eigentlich dachte ich über die Verwendung von functionName.toString () nach, aber ich hoffte auf etwas eleganteres (und vielleicht schnelleres)
sasha.sochka

2
@ sasha.sochka, kam hierher und fragte sich genau das Gleiche, nachdem festgestellt wurde, dass es keinen eingebauten Weg gab, um Parameternamen mit Javascript zu erhalten
Hart Simha

14
Um Menschen Zeit zu sparen, können Sie diese Funktion von Angular via annotate = angular.injector.$$annotate
Nick

3
Ich habe buchstäblich im Internet nach diesem Thema gesucht, weil ich neugierig war, wie Angular es gemacht hat ... jetzt weiß ich es und auch ich weiß zu viel!
Steven Hunt

40

Hier ist eine aktualisierte Lösung, die versucht, alle oben genannten Randfälle auf kompakte Weise anzugehen:

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 [""]
}  

Verkürzte Testausgabe (vollständige Testfälle sind unten beigefügt):

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


Dies wird unterbrochen, wenn einzeilige Kommentare vorhanden sind. Versuchen Sie dies: return (func+'') .replace(/[/][/].*$/mg,'') // strip single-line comments (line-ending sensitive, so goes first) .replace(/\s+/g,'') // remove whitespace
Merlyn Morgan-Graham

4
Sie sollten wahrscheinlich ersetzen func + ''mit Function.toString.call(func)verteidigen gegen den Fall , wenn die Funktion eine benutzerdefinierte .toString () Umsetzung hat.
Paul Go

1
fette Pfeile =>.split(/\)[\{=]/, 1)[0]
Matt

1
Dadurch werden destrukturierte Objekte (wie ({ a, b, c })) in alle Parameter innerhalb der Destrukturierung aufgeteilt. .split.split(/,(?![^{]*})/g)
Um

1
Dies funktioniert auch nicht, wenn es Standardzeichenfolgenwerte gibt, die "//" oder "/ *" enthalten
Skerit

23

Eine Lösung, die weniger fehleranfällig für Leerzeichen und Kommentare ist, wäre:

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

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

["hi", "you"]

1
@AlexMills Eine Sache, die mir aufgefallen ist, ist, dass die Spezifikation für Pfeilfunktionen besagt, dass sie nicht als "Funktionen" behandelt werden sollten. Dies bedeutet, dass dies nicht für die Array-Funktionen geeignet wäre. Das 'dies' ist nicht auf die gleiche Weise eingestellt und sie sollten auch nicht als Funktionen aufgerufen werden. Das habe ich auf die harte Tour gelernt. ($ myService) => $ myService.doSomething () sieht cool aus, ist aber ein Missbrauch der Array-Funktionen.
Andrew T Finnell

20

Viele der Antworten hier verwenden reguläre Ausdrücke. Dies ist in Ordnung, behandelt jedoch neue Ergänzungen der Sprache nicht so gut (wie Pfeilfunktionen und Klassen). Es ist auch zu beachten, dass, wenn Sie eine dieser Funktionen für minimierten Code verwenden, diese gehen wird 🔥. Es wird verwendet, was auch immer der minimierte Name ist. Angular umgeht dies, indem Sie ein geordnetes Array von Zeichenfolgen übergeben können, das der Reihenfolge der Argumente entspricht, wenn Sie sie im DI-Container registrieren. Also weiter mit der Lösung:

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

Dies behandelt das ursprüngliche Analyseproblem und einige weitere Funktionstypen (z. B. Pfeilfunktionen). Hier ist eine Vorstellung davon, was es so kann und was nicht:

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

Je nachdem, was Sie für ES6-Proxies verwenden möchten, ist die Destrukturierung möglicherweise die beste Wahl. Wenn Sie es beispielsweise für die Abhängigkeitsinjektion verwenden möchten (unter Verwendung der Namen der Parameter), können Sie dies wie folgt tun:

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

Es ist nicht der fortschrittlichste Resolver auf dem Markt, aber es gibt eine Vorstellung davon, wie Sie einen Proxy verwenden können, um damit umzugehen, wenn Sie den Args-Parser für einfache DI verwenden möchten. Es gibt jedoch eine kleine Einschränkung bei diesem Ansatz. Wir müssen Destrukturierungszuweisungen anstelle normaler Parameter verwenden. Wenn wir den Injektor-Proxy übergeben, entspricht die Destrukturierung dem Aufruf des Getters für das Objekt.

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

Dies gibt Folgendes aus:

{
    "tweeter": {
        "api": {
            "client": {}
        }
    },
    "timeline": {
        "api": {
            "client": {}
        }
    }
}

Es verkabelt die gesamte Anwendung. Das Beste daran ist, dass die App einfach zu testen ist (Sie können einfach jede Klasse instanziieren und Mocks / Stubs / etc übergeben). Auch wenn Sie Implementierungen austauschen müssen, können Sie dies von einem einzigen Ort aus tun. All dies ist aufgrund von JS-Proxy-Objekten möglich.

Hinweis: Es muss noch viel Arbeit geleistet werden, bevor es für die Produktion bereit ist, aber es gibt eine Vorstellung davon, wie es aussehen würde.

Es ist ein bisschen spät in der Antwort, aber es kann anderen helfen, die an das Gleiche denken. 👍


13

Ich weiß, dass dies eine alte Frage ist, aber Anfänger haben dies kopypastiert, als ob dies in jedem Code eine gute Praxis wäre. In den meisten Fällen verbirgt das Parsen der Zeichenfolgendarstellung einer Funktion zur Verwendung ihrer Parameternamen nur einen Fehler in der Logik des Codes.

Parameter einer Funktion werden tatsächlich in einem Array-ähnlichen Objekt namens aufgerufen arguments, wobei das erste Argument arguments[0]das zweite, das zweite arguments[1]usw. ist. Das Schreiben von Parameternamen in Klammern kann als Kurzsyntax angesehen werden. Dies:

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

...ist das gleiche wie:

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

    console.log("does something");
}

Die Variablen selbst werden im Funktionsumfang gespeichert, nicht als Eigenschaften in einem Objekt. Es gibt keine Möglichkeit, den Parameternamen über Code abzurufen, da es sich lediglich um ein Symbol handelt, das die Variable in der menschlichen Sprache darstellt.

Ich habe die Zeichenfolgendarstellung einer Funktion immer als Werkzeug für Debugging-Zwecke betrachtet, insbesondere aufgrund dieses argumentsArray-ähnlichen Objekts. Sie müssen den Argumenten zunächst keine Namen geben. Wenn Sie versuchen, eine String-Funktion zu analysieren, werden Sie nicht über zusätzliche unbenannte Parameter informiert, die möglicherweise erforderlich sind.

Hier ist eine noch schlimmere und häufigere Situation. Wenn eine Funktion mehr als 3 oder 4 Argumente hat, kann es logisch sein, ihr stattdessen ein Objekt zu übergeben, mit dem einfacher gearbeitet werden kann.

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

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

In diesem Fall kann die Funktion selbst das empfangene Objekt lesen, nach seinen Eigenschaften suchen und sowohl ihre Namen als auch ihre Werte abrufen. Wenn Sie jedoch versuchen, die Zeichenfolgendarstellung der Funktion zu analysieren, erhalten Sie nur "obj" für Parameter. das ist überhaupt nicht nützlich.


1
Ich denke, der Fall für so etwas ist normalerweise: Debuggen / Protokollieren, eine Art Dekorateur, der funky Sachen macht (Fachbegriff 😁), oder das Erstellen eines Abhängigkeitsinjektions-Frameworks für Ihre Apps, das automatisch basierend auf dem Argumentnamen injiziert wird (so eckig) funktioniert). Ein weiterer wirklich interessanter Anwendungsfall ist promisify-node (eine Bibliothek, die eine Funktion übernimmt, die normalerweise einen Rückruf entgegennimmt und diese dann in ein Promise konvertiert). Sie verwenden dies, um gebräuchliche Namen für Rückrufe zu finden (wie cb / callback / etc) und können dann überprüfen, ob die Funktion asynchron oder synchron ist, bevor sie sie umschließt.
James Drew

In dieser Datei finden Sie den Parser . Es ist ein bisschen naiv, aber es behandelt die meisten Fälle.
James Drew

Interessanterweise bin ich überrascht, dass eine solche Bibliothek Aufmerksamkeit erregt hat. Nun, es gibt mehrere offene Fragen zu problematischen Situationen wie den von mir beschriebenen. Wie gesagt, wenn es zu Debugging-Zwecken ist, ist es in Ordnung, aber es ist viel zu riskant, sich auf die Konvertierung von Funktionen in Produktionsumgebungen zu verlassen.
Domino

Auf einer "lustigen" Nebenbemerkung können Sie alle Funktionen so verhalten, als wären sie anonyme integrierte Funktionen, indem Sie Folgendes ausführen:Function.prototype.toString = function () { return 'function () { [native code] }'; };
Domino

1
Einverstanden, es ist ein bisschen chaotisch, so damit umzugehen. Die beste und einfachste Verwendung ist die Abhängigkeitsinjektion. In diesem Fall besitzen Sie den Code und können mit der Benennung und anderen Codebereichen umgehen. Ich denke, hier würde es am meisten Verwendung finden. Ich verwende derzeit Esprima (anstelle von Regex) und ES6 Proxy( Konstruktor-Trap und Apply- Trap ) und verwende ReflectionDI für einige meiner Module. Es ist ziemlich solide.
James Drew

10

Da JavaScript eine Skriptsprache ist, sollte die Selbstbeobachtung das Abrufen von Funktionsparameternamen unterstützen. Das Punting auf diese Funktionalität ist ein Verstoß gegen die ersten Prinzipien, deshalb habe ich beschlossen, das Problem weiter zu untersuchen.

Das führte mich zu dieser Frage, aber keine eingebauten Lösungen. Was mich dazu geführt hat dieser Antwort führte, die erklärt, dass sie argumentsnur außerhalb der Funktion veraltet ist, sodass wir sie nicht mehr verwenden können myFunction.argumentsoder erhalten:

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)als Standardwerte verwendet werden können. So Gaafar Antwort oder James Drew Antwort sind bisher die besten Ansätze.

Ich habe das Babylon ausprobiert und Esprima- Parser ausprobiert, aber leider können sie keine eigenständigen anonymen Funktionen analysieren, wie in Mateusz Charytoniuks Antwort ausgeführt . Ich habe jedoch eine andere Problemumgehung gefunden, indem ich den Code in Klammern gesetzt habe, um die Logik nicht zu ändern:

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

Die Zeilenumbrüche verhindern Probleme mit //(einzeiligen Kommentaren).

⭐ Wenn kein Parser verfügbar ist, besteht die nächstbeste Option darin, eine bewährte Technik wie die regulären Ausdrücke des Abhängigkeitsinjektors von Angular.js zu verwenden. Ich habe eine funktionale Version von Lambders Antwort mit kombiniert der Antwort und einen optionalen ARROWBooleschen Wert hinzugefügt, um zu steuern, ob die regulären Ausdrücke ES6- Fettpfeilfunktionen zulassen.


Hier sind zwei Lösungen, die ich zusammengestellt habe. Beachten Sie, dass diese keine Logik haben, um festzustellen, ob eine Funktion eine gültige Syntax hat. Sie extrahieren nur die Argumente. Dies ist im Allgemeinen in Ordnung, da wir normalerweise analysierte Funktionen an übergeben, getArguments()sodass deren Syntax bereits gültig ist.

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

Node.js-Version (kann erst ausgeführt werden, wenn 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 stoppt):

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


Wenn ich mich nicht irre, funktioniert in Ihrer Browserversion die erste Bedingung in FUNC_ARGS sowohl für Pfeil- als auch für herkömmliche Funktionen. Sie benötigen also nicht den zweiten Teil und können die Abhängigkeit von ARROW aufheben.
Pwilcox

Das ist toll! Ich suchte nach einer solchen Lösung, die einen Parser verwendet, um die ES6-Syntax abzudecken. Ich habe vor, dies zu verwenden, um einen Scherz-Matcher für die Implementierung der Schnittstelle zu erstellen, da die einfache Verwendung von function.length Einschränkungen bei den Standardparametern aufweist und ich in der Lage sein wollte, Ruheparameter zu aktivieren.
Cue8chalk

Es sei darauf hingewiesen, dass der fünfte Testfall, der Klammern im Standardwert enthält, derzeit fehlschlägt. Ich wünschte, mein Regex-Fu wäre stark genug, um es zu reparieren, sorry!
Dale Anderson

8

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 Einzeilerfunktion 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 wird zurückgegeben "abc,def,ghi,jkl"

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

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 erzeugt einen regulären Ausdruck mit dem new RegExp('(?:'+Function.name+'\\s*|^)\\s*\\((.*?)\\)'). Ich muss verwenden, new RegExpweil ich eine Variable ( Function.nameden Namen der Funktion, auf die abgezielt wird) in RegExp einfüge.

Beispiel Wenn der Funktionsname "foo" ( function foo()) lautet, lautet RegExp /foo\s*\((.*?)\)/.

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

Dann konvertiert es die gesamte Funktion in eine Zeichenfolge und entfernt alle Zeilenumbrüche. Das Entfernen von Zeilenumbrüchen hilft bei der Funktionseinstellung, die Camilo Martin gegeben hat.

.exec(...)[1]

Dies ist die RegExp.prototype.execFunktion. Grundsätzlich wird der reguläre Exponent ( new RegExp()) mit dem String ( Function.toString()) abgeglichen . Dann gibt das [1]die erste Erfassungsgruppe zurück, die im regulären Exponenten ( (.*?)) gefunden wurde.

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

Dadurch werden alle Kommentare in /*und entfernt */und alle Leerzeichen entfernt.


Dies hat auch jetzt unterstützt das Lesen und Verstehen Pfeil ( =>) Funktionen, wie zum Beispiel f = (a, b) => void 0;, in welchem Function.toString()zurückkehren würde (a, b) => void 0anstelle der normalen Funktion des function f(a, b) { return void 0; }. Der ursprüngliche reguläre Ausdruck hätte einen Fehler in seiner Verwirrung ausgelöst, wird aber jetzt berücksichtigt.

Die Änderung erfolgte von new RegExp(Function.name+'\\s*\\((.*?)\\)')( /Function\s*\((.*?)\)/) nach new RegExp('(?:'+Function.name+'\\s*|^)\\((.*?)\\)')( /(?:Function\s*|^)\((.*?)\)/)


Wenn Sie alle Parameter in ein Array anstatt in einen durch Kommas getrennten String umwandeln möchten, fügen Sie am Ende einfach hinzu .split(',').


Recht nett. Eine Zeile behandelt Pfeilfunktionen, keine externen Abhängigkeiten und deckt mehr als genug Randfälle ab, zumindest für meine beabsichtigte Verwendung. Wenn Sie vernünftige Annahmen über die Funktionssignaturen treffen können, ist dies alles, was Sie brauchen. Vielen Dank!
Charlie Roberts

Funktioniert nicht mit dieser einfachen Pfeilfunktion : f = (a, b) => void 0; auf getParameters(f)ichTypeError: Cannot read property '1' of null
AT

@AT Ich habe gerade die Antwort aktualisiert, um die Unterstützung für Ihr Problem zu beheben
Jaketr00

Danke ... aber denken Sie daran, dass keine Klammern mehr erforderlich sind, so dass Sie Dinge tun können getParameters(a => b => c => d => a*b*c*d), die mit Ihrem Code immer noch das ergeben TypeError: Cannot read property '1' of null... während dieser funktioniert stackoverflow.com/a/29123804
AT

Funktioniert nicht, wenn die Funktion Standardwerte hat (Rolle, Name = "Bob"). Der extrahierte Parameter ist Name = "Bob" anstelle des erwarteten "Namens"
Dmitri

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

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


7

Sie können auch den Parser "esprima" verwenden, um viele Probleme mit Kommentaren, Leerzeichen und anderen Dingen in 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"]

6

Ich habe es schon einmal versucht, aber nie einen praktischen Weg gefunden, dies zu erreichen. Ich habe stattdessen ein Objekt übergeben und es dann durchlaufen.

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

Ich habe zu viele Funktionen bereits vordefiniert, die mit Standardparametern anstelle eines einzelnen Objekts aufgerufen werden. Alles zu ändern würde zu viel Zeit in Anspruch nehmen.
Vikasde

2
Diese Antwort ist nicht die Antwort auf die ursprüngliche Frage, die genau definiert wurde. Es zeigt eine Lösung für ein völlig anderes Problem. Die ursprüngliche Frage bezieht sich auf die Technik, die AngularJS für seine Abhängigkeitsinjektion verwendet. Argumentnamen sind sinnvoll, da sie Abhängigkeiten des Moduls entsprechen, das DI automatisch bereitstellt.
Lambder

4

Ich weiß nicht, ob diese Lösung zu Ihrem Problem passt, aber Sie können damit jede gewünschte Funktion neu definieren, ohne den Code ändern zu müssen, der sie verwendet. Bestehende Aufrufe verwenden positionierte Parameter, während die Funktionsimplementierung möglicherweise "benannte Parameter" (einen einzelnen Hash-Parameter) verwendet.

Ich dachte, dass Sie ohnehin vorhandene Funktionsdefinitionen ändern werden. Warum also nicht eine Factory-Funktion, die genau das macht, was Sie wollen:

<!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>

Ich hoffe es hilft.


4

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 Code hier findet die Namen der drei (formalen) Parameter der Funktion f. Er tut dies durch die Fütterung fin acorn.parse().


was ist mit den Werten?
eran otzap

2

Ich weiß nicht, wie ich eine Liste der Parameter erhalten soll, aber Sie können dies tun, um zu ermitteln, wie viele erwartet werden.

alert(doSomething.length);

function gotcha (a, b = false, c) {}; alert(gotcha.length)
Balupton

2

Ausgehend von der Antwort von @ jack-allan habe ich die Funktion leicht geändert, um ES6-Standardeigenschaften wie die folgenden zuzulassen:

function( a, b = 1, c ){};

immer noch zurückkehren [ 'a', 'b' ]

/**
 * Get the keys of the paramaters of a function.
 *
 * @param {function} method  Function to get parameter keys for
 * @return {array}
 */
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
var ARGUMENT_NAMES = /(?:^|,)\s*([^\s,=]+)/g;
function getFunctionParameters ( func ) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '');
    var argsList = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')'));
    var result = argsList.match( ARGUMENT_NAMES );

    if(result === null) {
        return [];
    }
    else {
        var stripped = [];
        for ( var i = 0; i < result.length; i++  ) {
            stripped.push( result[i].replace(/[\s,]/g, '') );
        }
        return stripped;
    }
}

1
Danke, deine ist die einzige in diesem Thread, die für mich funktioniert hat.
AT

1

Wie ich es normalerweise mache:

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

Sie können die Argumente sogar anhand des Funktionsnamens wie folgt referenzieren:

name.arguments;

Hoffe das hilft!


4
Und wo sind die Namen der Funktionsparameter?
Andrej

Ah ... Du meinst, du willst es in Hash-Form? Als ob: var args = name.arguments; console.log('I WANNa SEE', args);etwas wie "{arg1: {...}, arg2: 'string'}" ausgeben? Dies könnte die Dinge klären : (function fn (arg, argg, arrrrgggg) { console.log('#fn:', fn.arguments, Object.keys(fn.arguments)); }); fn('Huh...?', 'Wha...?', 'Magic...?');. Funktionsargumente sind ein 'Array'-ähnliches Objekt mit aufzählbaren Indizes. Ich denke nicht, dass eine Hash-Zuordnung möglich ist, aber Sie könnten einfach ein Objekt-Literal übergeben, das eine gute Praxis ist, wenn Sie sowieso mehr als 4 Parameter haben.
Cody

1
//See this:


// global var, naming bB
var bB = 5;

//  Dependency Injection cokntroller
var a = function(str, fn) {
  //stringify function body
  var fnStr = fn.toString();

  // Key: get form args to string
  var args = fnStr.match(/function\s*\((.*?)\)/);
  // 
  console.log(args);
  // if the form arg is 'bB', then exec it, otherwise, do nothing
  for (var i = 0; i < args.length; i++) {
    if(args[i] == 'bB') {
      fn(bB);
    }
  }
}
// will do nothing
a('sdfdfdfs,', function(some){
alert(some)
});
// will alert 5

a('sdfdsdsfdfsdfdsf,', function(bB){
alert(bB)
});

// see, this shows you how to get function args in string

1

Die Antwort darauf erfordert 3 Schritte:

  1. Um die Werte der tatsächlichen Parameter zu erhalten, die an die Funktion übergeben wurden (nennen wir es argValues). Dies ist unkompliziert, da es argumentsinnerhalb der Funktion verfügbar sein wird .
  2. Um die Parameternamen aus der Funktionssignatur zu erhalten (nennen wir es argNames ). Dies ist nicht so einfach und erfordert das Parsen der Funktion. Anstatt den komplexen regulären Ausdruck selbst durchzuführen und sich um Randfälle (Standardparameter, Kommentare, ...) zu kümmern, können Sie eine Bibliothek wie babylon verwenden, die die Funktion in einen abstrakten Syntaxbaum analysiert, aus dem Sie die Namen der Parameter abrufen können.
  3. Der letzte Schritt besteht darin, die beiden Arrays zu einem Array zusammenzufügen, das den Namen und den Wert aller Parameter enthält.

Der Code wird so sein

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)

und das protokollierte Objekt wird sein

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

Und hier ist ein funktionierendes Beispiel: https://tonicdev.com/5763eb77a945f41300f62a79/5763eb77a945f41300f62a7a


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

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

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

    return argsObj;
}

1

Wow, so viele Antworten schon. Ich bin mir ziemlich sicher, dass dies begraben wird. Trotzdem dachte ich, dass dies für einige nützlich sein könnte.

Ich war mit den ausgewählten Antworten nicht ganz zufrieden, da es in ES6 mit Standardwerten nicht gut funktioniert. Außerdem werden die Standardwertinformationen nicht bereitgestellt. Ich wollte auch eine leichte Funktion, die nicht von einer externen Bibliothek abhängt.

Diese Funktion ist sehr nützlich für Debugging-Zwecke, zum Beispiel: Protokollierung der aufgerufenen Funktion mit ihren Parametern, Standardparameterwerten und Argumenten.

Ich habe gestern einige Zeit damit verbracht, das richtige RegExp zu knacken, um dieses Problem zu lösen, und das habe ich mir ausgedacht. Es funktioniert sehr gut und ich bin sehr zufrieden mit dem Ergebnis:

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

Wie Sie sehen können, verschwinden einige der Parameternamen, weil der Babel-Transpiler sie aus der Funktion entfernt. Wenn Sie dies im neuesten NodeJS ausführen würden, funktioniert es wie erwartet (Die kommentierten Ergebnisse stammen von NodeJS).

Ein weiterer Hinweis, wie im Kommentar angegeben, ist, dass er nicht mit Inline-Pfeilfunktionen als Standardwert funktioniert. Dies macht es einfach viel zu komplex, die Werte mit einem RegExp zu extrahieren.

Bitte lassen Sie mich wissen, ob dies für Sie nützlich war! Würde gerne ein Feedback hören!


1

Ich werde Ihnen unten ein kurzes Beispiel geben:

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

In diesem Beispiel wird nur der Name der Parameter für Sie ermittelt.
Myzhou

Diese Antwort ist nützlich, beantwortet aber nicht die Frage. Ich habe abgestimmt, weil es mein Problem gelöst hat, aber ich denke, es sollte an einen angemesseneren Ort gebracht werden. Vielleicht nach verwandten Fragen suchen?
Chharvey

1

Dieses Paket verwendet eine Neufassung, um einen AST zu erstellen, und anschließend werden die Parameternamen daraus gesammelt. Dadurch kann der Mustervergleich, Standardargumente, Pfeilfunktionen und andere ES6-Funktionen unterstützt werden.

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


1

Ich habe die Version von AngularJS geändert , die einen Abhängigkeitsinjektionsmechanismus implementiert, um ohne Angular zu funktionieren. Ich habe auch den STRIP_COMMENTSregulären Ausdruck für die Arbeit aktualisiert ECMA6, sodass er beispielsweise Standardwerte in der Signatur unterstützt.

var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /^\s*(_?)(.+?)\1\s*$/;
var STRIP_COMMENTS = /(\/\/.*$)|(\/\*[\s\S]*?\*\/)|(\s*=[^,\)]*(('(?:\\'|[^'\r\n])*')|("(?:\\"|[^"\r\n])*"))|(\s*=[^,\)]*))/mg;

function annotate(fn) {
  var $inject,
    fnText,
    argDecl,
    last;

  if (typeof fn == 'function') {
    if (!($inject = fn.$inject)) {
      $inject = [];
      fnText = fn.toString().replace(STRIP_COMMENTS, '');
      argDecl = fnText.match(FN_ARGS);
      argDecl[1].split(FN_ARG_SPLIT).forEach(function(arg) {
        arg.replace(FN_ARG, function(all, underscore, name) {
          $inject.push(name);
        });
      });
      fn.$inject = $inject;
    }
  } else {
    throw Error("not a function")
  }
  return $inject;
}

console.log("function(a, b)",annotate(function(a, b) {
  console.log(a, b, c, d)
}))
console.log("function(a, b = 0, /*c,*/ d)",annotate(function(a, b = 0, /*c,*/ d) {
  console.log(a, b, c, d)
}))
annotate({})


0

Sie können auf die an eine Funktion übergebenen Argumentwerte mit der Eigenschaft "arguments" zugreifen.

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

Sind Argumente nicht veraltet? Siehe den Vorschlag von Ionut G. Stan oben.
Vikasde

Vikasde ist richtig. Der Zugriff auf die argumentsEigenschaft einer Funktionsinstanz ist veraltet. Siehe developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
Ionuț G. Stan

0

Es ist ziemlich einfach.

Am ersten gibt es eine veraltete arguments.callee- einen Verweis auf die aufgerufene Funktion. Im zweiten Schritt, wenn Sie einen Verweis auf Ihre Funktion haben, können Sie leicht deren Textdarstellung erhalten. Beim dritten Aufruf Ihrer Funktion als Konstruktor können Sie auch einen Link über yourObject.constructor haben. NB: Die erste Lösung ist veraltet. Wenn Sie sie nicht verwenden können, müssen Sie auch über Ihre App-Architektur nachdenken. Wenn Sie keine genauen Variablennamen benötigen, verwenden Sie diese einfach innerhalb einer funktionalen internen Variablen argumentsohne Magie.

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

Alle werden toString aufrufen und durch re ersetzen, damit wir einen Helfer erstellen können:

// getting names of declared parameters
var getFunctionParams = function (func) {
    return String(func).replace(/[^\(]+\(([^\)]*)\).*/m, '$1');
}

Einige Beispiele:

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

Viel Spaß mit JS!

UPD: Jack Allan wurde tatsächlich eine etwas bessere Lösung angeboten. GJ Jack!


Dies könnte noch einfacher sein, wenn Sie SomeFuncNameanstelle von verwenden arguments.callee(beide zeigen auf das Funktionsobjekt selbst).
Raphael Schweikert

0

Was auch immer die Lösung sein mag, es darf nicht bei seltsamen Funktionen brechen, deren toString()Aussehen genauso seltsam ist:

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

Screenshot von der Konsole

Warum sollten Sie auch komplexe reguläre Ausdrücke verwenden? Dies kann wie folgt erfolgen:

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

Dies funktioniert überall mit jeder Funktion, und der einzige reguläre Ausdruck ist das Entfernen von Leerzeichen, bei dem aufgrund des .splitTricks nicht einmal die gesamte Zeichenfolge verarbeitet wird .


0

Ok, also eine alte Frage mit vielen angemessenen Antworten. Hier ist mein Angebot, das keinen regulären Ausdruck verwendet, außer für die einfache Aufgabe, Leerzeichen zu entfernen. (Ich sollte beachten, dass die Funktion "strips_comments" sie tatsächlich ausräumt, anstatt sie physisch zu entfernen. Das liegt daran, dass ich sie an anderer Stelle verwende und aus verschiedenen Gründen die Positionen der ursprünglichen Token ohne Kommentar benötigen, um intakt zu bleiben.)

Es ist ein ziemlich langer Codeblock, da dieses Einfügen ein Mini-Test-Framework enthält.

    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

Hier ist eine Möglichkeit:

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

Hinweis: Dies funktioniert nur in Browsern, die dies unterstützen arguments.callee.


2
Die while-Schleife führt zu einer Endlosschleife mit dem bereitgestellten Code (Stand 20171121at1047EDT)
George 2.0 Hope

@ George2.0Hope Danke, dass du darauf hingewiesen hast. Ich werde die Antwort aktualisieren.
Ates Goral

args.toSource ist keine Funktion (Zeile 20) ... aber selbst wenn Sie sie ändern in: console.log (args.toString ()); ... Sie erhalten ... [Objekt Objekt] ... besser, wenn Sie dies tun: console.log (JSON.stringify (args));
George 2.0 Hope

Die Dinge haben sich seit 2009 ziemlich verändert!
Ates Goral

1
Falls hier jemand zu React auftaucht, funktioniert diese Funktion, die fantastisch ist, nicht im strengen Modus.
Individual11

0

Hinweis: Wenn Sie die Destrukturierung von ES6-Parametern mit der Top-Lösung verwenden möchten, fügen Sie die folgende Zeile hinzu.

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

-1

Funktionsparameter Zeichenfolge Wert Bild dynamisch aus JSON . Da item.product_image2 eine URL-Zeichenfolge ist, müssen Sie sie in Anführungszeichen setzen, wenn Sie den Parameter changeImage aufrufen.

Meine Funktion Klicken Sie auf

items+='<img src='+item.product_image1+' id="saleDetailDivGetImg">';
items+="<img src="+item.product_image2+"  onclick='changeImage(\""+item.product_image2+"\");'>";

Meine Funktion

<script type="text/javascript">
function changeImage(img)
 {
    document.getElementById("saleDetailDivGetImg").src=img;
    alert(img);
}
</script>
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.