Molte delle risposte qui usano regex, questo va bene ma non gestisce troppo bene le nuove aggiunte al linguaggio (come le funzioni e le classi delle frecce). Inoltre, nota che se usi una di queste funzioni su un codice minimizzato andrà 🔥. Userà qualunque sia il nome minimizzato. Angular aggira il problema consentendo di passare un array ordinato di stringhe che corrisponde all'ordine degli argomenti durante la registrazione con il contenitore DI. Quindi, con la soluzione:
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');
};
In questo modo viene risolto il problema di analisi originale e alcuni altri tipi di funzioni (ad es. Funzioni freccia). Ecco un'idea di cosa può e non può gestire così com'è:
// 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);
});
});
A seconda di cosa vuoi usarlo per i proxy ES6 e la destrutturazione potrebbe essere la soluzione migliore. Ad esempio, se si desidera utilizzarlo per l'iniezione delle dipendenze (utilizzando i nomi dei parametri), è possibile farlo come segue:
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);
}
}
}
Non è il risolutore più avanzato in circolazione, ma dà un'idea di come è possibile utilizzare un proxy per gestirlo se si desidera utilizzare args parser per semplici DI. Vi è tuttavia un leggero avvertimento in questo approccio. Dobbiamo usare compiti di destrutturazione invece di parametri normali. Quando passiamo nel proxy dell'iniettore, la destrutturazione è la stessa di chiamare il getter sull'oggetto.
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));
Ciò genera quanto segue:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Ha cablato l'intera applicazione. La cosa migliore è che l'app è facile da testare (puoi solo istanziare ogni classe e passare in beffe / stub / ecc.). Inoltre, se è necessario scambiare le implementazioni, è possibile farlo da un'unica posizione. Tutto ciò è possibile grazie agli oggetti JS Proxy.
Nota: c'è molto lavoro da fare prima di essere pronto per l'uso in produzione, ma dà un'idea di come sarebbe.
È un po 'tardi nella risposta, ma può aiutare gli altri a pensare la stessa cosa. 👍