У багатьох відповідях тут використовуються регулярні вирази, це добре, але він не надто добре обробляє нові доповнення до мови (наприклад, функції стрілок та класи). Також слід зауважити, що якщо ви використовуєте будь-яку з цих функцій для мінімізованого коду, вона піде 🔥. Він буде використовувати будь-яку мінімізовану назву. Кутовий обходить це, дозволяючи передати впорядкований масив рядків, який відповідає порядку аргументів при реєстрації їх у контейнері DI. Отже, з рішенням:
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');
};
Це обробляє оригінальну проблему розбору та ще кілька типів функцій (наприклад, функції стрілок). Ось ідея про те, що може, а що не може впоратися так:
// 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);
});
});
Залежно від того, що ви хочете використовувати для проксі-серверів ES6 та деструктуризації, можливо, найкраща ставка. Наприклад, якщо ви хотіли використовувати його для введення залежності (використовуючи назви парам), ви можете зробити це наступним чином:
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);
}
}
}
Це не самий просунутий резолютор, але він дає уявлення про те, як ви можете використовувати проксі для обробки, якщо ви хочете використовувати аргумент-аналізатор для простого DI. Однак у цьому підході є один невеликий застереження. Нам потрібно використовувати завдання деструктирування замість звичайних парам. Коли ми передаємо проксі-інжектор, то руйнування відбувається так само, як викликати геттера на об'єкт.
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));
Це виводить наступне:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Його з'єднали всю програму. Найкраще, що додаток легко перевірити (ви можете просто інстанціювати кожен клас і передавати макети / заглушки / тощо). Також якщо вам потрібно поміняти місцями реалізації, ви можете зробити це з одного місця. Все це можливо через об’єкти JS Proxy.
Примітка: Для цього потрібно зробити багато роботи, перш ніж вона буде готова до використання у виробництві, але вона дає уявлення про те, як вона виглядатиме.
У відповіді це трохи пізно, але це може допомогти іншим, хто думає про те саме. 👍