Во многих ответах здесь используются регулярные выражения, это нормально, но он не слишком хорошо обрабатывает новые дополнения к языку (например, функции стрелок и классы). Также следует отметить, что если вы используете любую из этих функций в минимизированном коде, она пойдет ????. Он будет использовать любое минифицированное имя. Angular обходит это, позволяя передавать упорядоченный массив строк, который соответствует порядку аргументов при их регистрации в контейнере 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);
}
}
}
Это не самый продвинутый преобразователь, но он дает представление о том, как вы можете использовать прокси для его обработки, если вы хотите использовать парсер args для простого 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": {}
}
}
}
Он подключил все приложение. Самое приятное то, что приложение легко тестировать (вы можете просто создать экземпляр каждого класса и передать mocks / stubs / и т. Д.). Также, если вам нужно поменять реализации, вы можете сделать это из одного места. Все это возможно благодаря объектам JS Proxy.
Примечание. Для этого необходимо проделать много работы, прежде чем оно будет готово к производственному использованию, но оно дает представление о том, как это будет выглядеть.
Это немного поздно с ответом, но это может помочь другим, которые думают о том же. ????
person
James Drew
schedule
07.01.2017