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. 👍