Beaucoup de réponses ici utilisent des expressions rationnelles, c'est bien mais cela ne gère pas trop bien les nouveaux ajouts au langage (comme les fonctions fléchées et les classes). Il est également à noter que si vous utilisez l'une de ces fonctions sur du code minifié, cela ira 🔥. Il utilisera le nom minifié. Angular contourne cela en vous permettant de passer dans un tableau ordonné de chaînes qui correspond à l'ordre des arguments lors de leur enregistrement dans le conteneur DI. Ainsi de suite avec la solution:
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');
};
Cela gère le problème d'analyse d'origine et quelques autres types de fonctions (par exemple les fonctions fléchées). Voici une idée de ce qu'il peut et ne peut pas gérer tel quel:
// 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);
});
});
Selon ce que vous voulez l'utiliser pour les proxys ES6 et la déstructuration peut être votre meilleur pari. Par exemple, si vous souhaitez l'utiliser pour l'injection de dépendances (en utilisant les noms des paramètres), vous pouvez le faire comme suit:
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);
}
}
}
Ce n'est pas le résolveur le plus avancé, mais il donne une idée de la façon dont vous pouvez utiliser un proxy pour le gérer si vous souhaitez utiliser l'analyseur args pour une DI simple. Il y a cependant une légère mise en garde dans cette approche. Nous devons utiliser des affectations de déstructuration au lieu de paramètres normaux. Lorsque nous passons dans le proxy injecteur, la déstructuration équivaut à appeler le getter sur l'objet.
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));
Cela génère les éléments suivants:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Son câblé l'ensemble de l'application. Le meilleur est que l'application est facile à tester (vous pouvez simplement instancier chaque classe et passer dans des mocks / stubs / etc). De plus, si vous devez échanger des implémentations, vous pouvez le faire à partir d'un seul endroit. Tout cela est possible grâce aux objets JS Proxy.
Remarque: il y a beaucoup de travail qui devrait être fait pour cela avant qu'il ne soit prêt pour une utilisation en production, mais cela donne une idée de ce à quoi il ressemblerait.
C'est un peu tard dans la réponse, mais cela peut aider ceux qui pensent à la même chose. 👍