Buradaki cevapların çoğu regexes kullanıyor, bu iyi ama dile yeni eklemeler (ok fonksiyonları ve sınıflar gibi) ile çok iyi başa çıkmıyor. Ayrıca, bu işlevlerden herhangi birini küçültülmüş kodda kullanırsanız, gitmesi gerektiğini unutmayın. Küçültülmüş ad ne olursa olsun kullanır. Açısal, DI kabına kaydederken, değişkenlerin sırasıyla eşleşen sıralı bir dizeler dizisi geçirmenize izin vererek bu sorunu çözer. Böylece çözüm:
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');
};
Bu, orijinal ayrıştırma sorununu ve birkaç işlev türünü daha (örneğin ok işlevleri) ele alır. İşte neyi işleyebileceği ve işleyemediği hakkında bir fikir:
// 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 Proxy'leri için ne kullanmak istediğinize bağlı olarak yıkım en iyi seçenek olabilir. Örneğin, bağımlılık enjeksiyonu için (param adlarını kullanarak) kullanmak isterseniz, aşağıdaki gibi yapabilirsiniz:
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);
}
}
}
Buradaki en gelişmiş çözümleyici değil, ancak basit DI için args ayrıştırıcısını kullanmak istiyorsanız, Proxy'yi nasıl kullanacağınız hakkında bir fikir verir. Bununla birlikte, bu yaklaşımda hafif bir uyarı vardır. Normal parametreler yerine yıkıcı ödevler kullanmalıyız. Enjektör proxy'sini geçtiğimizde yıkım, nesne üzerinde alıcıyı çağırmakla aynıdır.
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));
Bu, aşağıdakileri çıkarır:
{
"tweeter": {
"api": {
"client": {}
}
},
"timeline": {
"api": {
"client": {}
}
}
}
Tüm uygulama kadar kablolu. En iyi bit, uygulamanın test edilmesi kolay olmasıdır (sadece her sınıfı somutlaştırabilir ve alaycı / taslaklar / vb. Ayrıca, uygulamaları değiştirmeniz gerekiyorsa, bunu tek bir yerden yapabilirsiniz. Tüm bunlar JS Proxy nesneleri nedeniyle mümkündür.
Not: Üretimde kullanıma hazır hale gelmeden önce yapılması gereken çok iş vardır, ancak neye benzeyeceği hakkında bir fikir verir.
Cevabın biraz geç ama aynı şeyi düşünen başkalarına yardımcı olabilir. 👍