Viele der Antworten auf hier verwenden Regexes, das ist in Ordnung, aber es behandelt nicht neue Ergänzungen zu der Sprache zu gut (wie Pfeil-Funktionen und Klassen). Außerdem ist zu beachten, dass, wenn Sie eine dieser Funktionen auf verkleinerten Code verwenden, wird es zu gehen. Es wird verwendet, was auch immer der minifizierte Name ist. Angular umgeht dies, indem es Ihnen erlaubt, ein geordnetes Array von Strings zu übergeben, das mit der Reihenfolge der Argumente übereinstimmt, 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 Parse-Problem und einige weitere Funktionstypen (z. B. Pfeilfunktionen). Hier ist eine Vorstellung davon, was es kann und was nicht, wie es ist:
// 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, wofür Sie es verwenden möchten ES6 Proxies und destructuring kann Ihre beste Wette sein. Zum Beispiel, wenn Sie es für die Injektion von Abhängigkeiten (mit den Namen der Parameter) verwenden möchten, dann können Sie es 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, den es gibt, aber er gibt eine Vorstellung davon, wie man einen Proxy verwenden kann, um ihn zu handhaben, wenn man den args-Parser für einfache DI verwenden möchte. Es gibt jedoch einen kleinen Vorbehalt bei diesem Ansatz. Wir müssen Destrukturierungszuweisungen anstelle von normalen Params verwenden. Wenn wir den Injektor-Proxy übergeben, ist die Destrukturierung dasselbe wie der Aufruf des Getters auf dem 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));
Es wird Folgendes ausgegeben:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Es hat die gesamte Anwendung verkabelt. Das beste Bit ist, dass die App einfach zu testen ist (Sie können einfach jede Klasse instanziieren und in Mocks/Stubs/etc übergeben). Auch wenn Sie Implementierungen austauschen müssen, können Sie das von einem einzigen Ort aus tun. All dies ist möglich, weil der JS Proxy-Objekte.
Hinweis: Es muss noch viel daran gearbeitet werden, bevor es produktionsreif ist, aber es vermittelt eine Vorstellung davon, wie es aussehen könnte.
Die Antwort kommt zwar etwas spät, aber vielleicht hilft sie anderen, die an das Gleiche denken.