admin 管理员组文章数量: 1086019
I'm implementing a certain dependency injection solution for ES classes, and for it I need to know exact names of class constructor parameters.
When I get string form of class or class static method, it does give full code (as usually for functions), but for class constructor it does not.
class C { constructor(a, b) { }; static m(x,y) { } }
console.log(C);
console.log(C.constructor);
console.log(C.m);
results in
class C { constructor(a, b) { }; static m(x,y) { } }
ƒ Function() { [native code] }
ƒ m(x,y) { }
As result, I have to parse whole class code to extract constructor arguments part, using regex like this
C.toString().split(/constructor\s*[^\(]*\(\s*([^\)]*)\)/m)
Is there any cleaner way to get constructor argument names?
Update: Thank you for all your opinions and ments but I'm crystal aware how minification works, how TS decorators work and how Angular/AngularJS DI is implemented and how it works. It is not related to the question. The question is:
Is it possible to get constructor code as it is possible for function?
I'm implementing a certain dependency injection solution for ES classes, and for it I need to know exact names of class constructor parameters.
When I get string form of class or class static method, it does give full code (as usually for functions), but for class constructor it does not.
class C { constructor(a, b) { }; static m(x,y) { } }
console.log(C);
console.log(C.constructor);
console.log(C.m);
results in
class C { constructor(a, b) { }; static m(x,y) { } }
ƒ Function() { [native code] }
ƒ m(x,y) { }
As result, I have to parse whole class code to extract constructor arguments part, using regex like this
C.toString().split(/constructor\s*[^\(]*\(\s*([^\)]*)\)/m)
Is there any cleaner way to get constructor argument names?
Update: Thank you for all your opinions and ments but I'm crystal aware how minification works, how TS decorators work and how Angular/AngularJS DI is implemented and how it works. It is not related to the question. The question is:
Is it possible to get constructor code as it is possible for function?
Share Improve this question edited Sep 12, 2019 at 14:00 setec asked Sep 12, 2019 at 11:26 setecsetec 16.1k3 gold badges38 silver badges51 bronze badges 2- I don't think so. A class is its constructor. The body of the constructor function itself is not stored anywhere. – georg Commented Sep 12, 2019 at 11:56
-
Minifiers and transpilers will happily rename variables and function arguments, so you should not rely on
.toString()
behavior of anything. If you need strings to use for injection, then your injection framework should require people to specify them explicitly. – loganfsmyth Commented Sep 12, 2019 at 13:00
2 Answers
Reset to default 3A parameter's name should not be considered as a reliable way to identify what to inject into a constructor
. As you noticed, JavaScript is not designed to let you retrieve these names in a proper way, and if you're in a browser context, you're probably minifying the code before releasing it, causing the loss of the names.
Dependency injection mechanisms in JavaScript usually rely on metadata and custom build processes, such as Angular's class metadata piler that introspects the source code to generate runtime code.
That being said, if you're using TypeScript, here is a minimal example of how to achieve dependency injection with parameter decorators, by attaching metadata to the class itself:
const metadataKey = Symbol();
interface InjectableClass {
new (...args: any[]): any;
[metadataKey]: any[];
}
function Inject(value: any) {
return function (target: InjectableClass, key: PropertyKey, paramIndex: number) {
target[metadataKey] = Object.assign(target[metadataKey] || [], { [paramIndex]: value });
}
}
class Test {
static [metadataKey]: any[];
greetings: string;
name: string;
constructor(@Inject('Hello there!') greetings: string, @Inject('Guerric') name: string) {
this.greetings = greetings;
this.name = name;
}
sayHello() {
console.log(`${this.greetings} My name is ${this.name}`);
}
}
function factory<T extends InjectableClass>(clazz: T): InstanceType<T> {
return new clazz(...clazz[metadataKey]);
}
factory(Test).sayHello();
Which produces the following JavaScript:
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
const metadataKey = Symbol();
function Inject(value) {
return function (target, key, paramIndex) {
target[metadataKey] = Object.assign(target[metadataKey] || [], { [paramIndex]: value });
};
}
let Test = class Test {
constructor(greetings, name) {
this.greetings = greetings;
this.name = name;
}
sayHello() {
console.log(`${this.greetings} My name is ${this.name}`);
}
};
Test = __decorate([
__param(0, Inject('Hello there!')),
__param(1, Inject('Guerric')),
__metadata("design:paramtypes", [String, String])
], Test);
function factory(clazz) {
return new clazz(...clazz[metadataKey]);
}
factory(Test).sayHello();
TypeScript playground
Variant that uses a dedicated Map
in order to store the metadata instead of attaching them to the classes:
const metadataMap = new Map();
interface Constructable {
new (...args: any[]): any;
}
function Inject(value: any) {
return function (target: Constructable, key: PropertyKey, paramIndex: number) {
metadataMap.set(target, Object.assign(metadataMap.get(target) || [], { [paramIndex]: value }));
}
}
class Test {
greetings: string;
name: string;
constructor(@Inject('Hello there!') greetings: string, @Inject('Guerric') name: string) {
this.greetings = greetings;
this.name = name;
}
sayHello() {
console.log(`${this.greetings} My name is ${this.name}`);
}
}
function factory<T extends Constructable>(clazz: T): InstanceType<T> {
return new clazz(...metadataMap.get(clazz));
}
factory(Test).sayHello();
TypeScript playground
See here: https://replit./@trevorhreed/parse-class-constructor-params#index.js
var acorn = require("acorn")
class A {
constructor(p1, p2) { }
}
const getClassConstructorParams = cls => {
const ast = acorn.parse(cls.toString(), {
ecmaVersion: 2020
})
return ast.body[0].body.body
.find(x => {
return x.type === 'MethodDefinition'
&& x.kind === 'constructor'
})
.value
.params.map(x => x.name)
}
console.dir(
getClassConstructorParams(A)
)
本文标签: javascriptGet class constructor argument namesStack Overflow
版权声明:本文标题:javascript - Get class constructor argument names - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/p/1744040838a2523224.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论