คำตอบมากมายในที่นี้ใช้ regexes นี่ใช้ได้ แต่มันไม่สามารถจัดการกับสิ่งใหม่ ๆ ในภาษาได้เป็นอย่างดี (เช่นฟังก์ชั่นลูกศรและคลาส) สิ่งที่ควรทราบอีกอย่างคือถ้าคุณใช้ฟังก์ชั่นใด ๆ เหล่านี้กับรหัสย่อส่วนมันจะไป go มันจะใช้ชื่อที่ย่อเล็กสุด Angular ได้รับสิ่งนี้โดยอนุญาตให้คุณส่งผ่านสตริงที่เรียงลำดับซึ่งตรงกับลำดับของอาร์กิวเมนต์เมื่อทำการลงทะเบียนกับ DI container ดังนั้นด้วยวิธีการแก้ปัญหา:
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 Proxies และการทำลายล้างอาจเป็นทางออกที่ดีที่สุดของคุณ ตัวอย่างเช่นหากคุณต้องการใช้สำหรับการฉีดพึ่งพา (ใช้ชื่อของ params) จากนั้นคุณสามารถทำได้ดังนี้:
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);
}
}
}
มันไม่ใช่ตัวจำแนกขั้นสูงที่สุด แต่ให้แนวคิดว่าคุณสามารถใช้ Proxy เพื่อจัดการได้อย่างไรถ้าคุณต้องการใช้ args parser สำหรับ 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 / etc) นอกจากนี้หากคุณต้องการสลับการใช้งานคุณสามารถทำได้จากที่เดียว ทั้งหมดนี้เป็นไปได้เพราะวัตถุ JS Proxy
หมายเหตุ: มีงานจำนวนมากที่ต้องทำก่อนที่มันจะพร้อมสำหรับการใช้งานจริง แต่ให้แนวคิดว่ามันจะเป็นอย่างไร
มันค่อนข้างช้าในการตอบ แต่มันอาจช่วยคนอื่นที่กำลังคิดถึงสิ่งเดียวกัน 👍