最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Get class constructor argument names - Stack Overflow

programmeradmin1浏览0评论

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
Add a ment  | 

2 Answers 2

Reset to default 3

A 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)
)
发布评论

评论列表(0)

  1. 暂无评论