获取类的属性



是否有办法在TypeScript中获得类的属性名称?

在示例中,我想"描述"类A或任何类,并获得其属性的数组(也许只有public的?),这是可能的吗?还是应该先实例化对象?

class A {
    private a1;
    private a2;
    /** Getters and Setters */
}
class Describer<E> {
    toBeDescribed:E ;
    describe(): Array<string> {
        /**
         * Do something with 'toBeDescribed'                          
         */
        return ['a1', 'a2']; //<- Example
    }
}
let describer = new Describer<A>();
let x= describer.describe();
/** x should be ['a1', 'a2'] */ 

这个TypeScript代码

class A {
    private a1;
    public a2;
}

编译成以下JavaScript代码

class A {
}

这是因为JavaScript中的属性只有在它们具有一些值之后才开始存在。你必须给这些属性赋值。

class A {
    private a1 = "";
    public a2 = "";
}

编译成

class A {
    constructor() {
        this.a1 = "";
        this.a2 = "";
    }
}

但是,你不能仅仅从类中获得属性(你只能从原型中获得方法)。您必须创建一个实例。然后通过调用Object.getOwnPropertyNames()获得属性。

let a = new A();
let array = return Object.getOwnPropertyNames(a);
array[0] === "a1";
array[1] === "a2";

应用于您的示例

class Describer {
    static describe(instance): Array<string> {
        return Object.getOwnPropertyNames(instance);
    }
}
let a = new A();
let x = Describer.describe(a);

有些答案是部分错误的,其中的一些事实也是部分错误的。

回答你的问题:是的!你可以。

在打印稿

class A {
    private a1;
    private a2;

}

在Javascript中生成以下代码:

var A = /** @class */ (function () {
    function A() {
    }
    return A;
}());

正如@Erik_Cupal所说,你可以这样做:

let a = new A();
let array = return Object.getOwnPropertyNames(a);

但是这是不完整的。如果你的类有一个自定义构造函数会发生什么?你需要对Typescript做一个小技巧,因为它不会编译。你需要分配为any:

let className:any = A;
let a = new className();// the members will have value undefined

一般的解决方案是:

class A {
    private a1;
    private a2;
    constructor(a1:number, a2:string){
        this.a1 = a1;
        this.a2 = a2;
    }
}
class Describer{
   describeClass( typeOfClass:any){
       let a = new typeOfClass();
       let array = Object.getOwnPropertyNames(a);
       return array;//you can apply any filter here
   }
}

为了更好地理解将根据上下文引用。

另一个解决方案,您可以像这样迭代对象键,注意:您必须使用具有现有属性的实例化对象:

printTypeNames<T>(obj: T) {
    const objectKeys = Object.keys(obj) as Array<keyof T>;
    for (let key of objectKeys)
    {
       console.log('key:' + key);
    }
}

只是为了好玩

class A {
    private a1 = void 0;
    private a2 = void 0;
}
class B extends A {
    private a3 = void 0;
    private a4 = void 0;
}
class C extends B {
    private a5 = void 0;
    private a6 = void 0;
}
class Describer {
    private static FRegEx = new RegExp(/(?:this.)(.+?(?= ))/g); 
    static describe(val: Function, parent = false): string[] {
        var result = [];
        if (parent) {
            var proto = Object.getPrototypeOf(val.prototype);
            if (proto) {
                result = result.concat(this.describe(proto.constructor, parent));
            } 
        }
        result = result.concat(val.toString().match(this.FRegEx) || []);
        return result;
    }
}
console.log(Describer.describe(A)); // ["this.a1", "this.a2"]
console.log(Describer.describe(B)); // ["this.a3", "this.a4"]
console.log(Describer.describe(C, true)); // ["this.a1", ..., "this.a6"]

更新:如果你使用自定义构造函数,此功能将中断。

其他答案主要是获取对象的所有名称,要获取属性的值,可以使用yourObj[name],例如:

var propNames = Object.getOwnPropertyNames(yourObj);
propNames.forEach(
    function(propName) {
        console.log(
           'name: ' + propName 
        + ' value: ' + yourObj[propName]);
    }
);

我目前正在为Typescript开发一个类似linq的库,并希望在Typescript/Javascript中实现类似c#的GetProperties的东西。我使用Typescript和泛型的次数越多,我就越清楚地认识到,通常必须有一个具有初始化属性的实例化对象,才能在运行时获得有关类属性的任何有用信息。但无论如何,从构造函数对象或对象数组中检索信息是很好的,并且要灵活。

这是我现在得到的结果。

首先,我定义Array原型方法(c#开发人员称之为"扩展方法")。

export { } //creating a module of below code
declare global {
  interface Array<T> {
    GetProperties<T>(TClass: Function, sortProps: boolean): string[];
} }

GetProperties方法是这样的,灵感来自madreason的回答。

if (!Array.prototype.GetProperties) {
  Array.prototype.GetProperties = function <T>(TClass: any = null, sortProps: boolean = false): string[] {
    if (TClass === null || TClass === undefined) {
      if (this === null || this === undefined || this.length === 0) {
        return []; //not possible to find out more information - return empty array
      }
    }
    // debugger
    if (TClass !== null && TClass !== undefined) {
      if (this !== null && this !== undefined) {
        if (this.length > 0) {
          let knownProps: string[] = Describer.describe(this[0]).Where(x => x !== null && x !== undefined);
          if (sortProps && knownProps !== null && knownProps !== undefined) {
            knownProps = knownProps.OrderBy(p => p);
          }
          return knownProps;
        }
        if (TClass !== null && TClass !== undefined) {
          let knownProps: string[] = Describer.describe(TClass).Where(x => x !== null && x !== undefined);
          if (sortProps && knownProps !== null && knownProps !== undefined) {
            knownProps = knownProps.OrderBy(p => p);
          }
          return knownProps;
        }
      }
    }
    return []; //give up..
  }
}

描述符方法与madreason的答案大致相同。它可以处理类函数和如果你得到一个对象。然后它将使用Object。

如果没有给出类函数(即c#开发人员的类'type')。
class Describer {
  private static FRegEx = new RegExp(/(?:this.)(.+?(?= ))/g);
  static describe(val: any, parent = false): string[] {
    let isFunction = Object.prototype.toString.call(val) == '[object Function]';
    if (isFunction) {
      let result = [];
      if (parent) {
        var proto = Object.getPrototypeOf(val.prototype);
        if (proto) {
          result = result.concat(this.describe(proto.constructor, parent));
        }
      }
      result = result.concat(val.toString().match(this.FRegEx));
      result = result.Where(r => r !== null && r !== undefined);
      return result;
    }
    else {
      if (typeof val == "object") {
        let knownProps: string[] = Object.getOwnPropertyNames(val);
        return knownProps;
      }
    }
    return val !== null ? [val.tostring()] : [];
  }
}

这里你可以看到两个规范,用来测试Jasmine。

class Hero {
  name: string;
  gender: string;
  age: number;
  constructor(name: string = "", gender: string = "", age: number = 0) {
    this.name = name;
    this.gender = gender;
    this.age = age;
  }
}
class HeroWithAbility extends Hero {
  ability: string;
  constructor(ability: string = "") {
    super();
    this.ability = ability;
  }
}
describe('Array Extensions tests for TsExtensions Linq esque library', () => {
  it('can retrieve props for a class items of an array', () => {
    let heroes: Hero[] = [<Hero>{ name: "Han Solo", age: 44, gender: "M" }, <Hero>{ name: "Leia", age: 29, gender: "F" }, <Hero>{ name: "Luke", age: 24, gender: "M" }, <Hero>{ name: "Lando", age: 47, gender: "M" }];
    let foundProps = heroes.GetProperties(Hero, false);
    //debugger
    let expectedArrayOfProps = ["name", "age", "gender"];
    expect(foundProps).toEqual(expectedArrayOfProps);
    expect(heroes.GetProperties(Hero, true)).toEqual(["age", "gender", "name"]);
  });
  it('can retrieve props for a class only knowing its function', () => {
    let heroes: Hero[] = [];
    let foundProps = heroes.GetProperties(Hero, false);
    let expectedArrayOfProps = ["this.name", "this.gender", "this.age"];
    expect(foundProps).toEqual(expectedArrayOfProps);
    let foundPropsThroughClassFunction = heroes.GetProperties(Hero, true);
    //debugger
    expect(foundPropsThroughClassFunction.SequenceEqual(["this.age", "this.gender", "this.name"])).toBe(true);
  });

正如madreason所提到的,你必须初始化props以从类Function本身获取任何信息,否则当Typescript代码转换为Javascript代码时,它将被剥离。

Typescript 3.7在泛型方面做得很好,但是由于有c#和反射的背景,Typescript和泛型的一些基本部分仍然让人感觉有些松散和未完成的事情。就像我这里的代码一样,但至少我得到了我想要的信息——给定类或对象实例的属性名称列表。

SequenceEqual是这个方法:

    if (!Array.prototype.SequenceEqual) {
  Array.prototype.SequenceEqual = function <T>(compareArray: T): boolean {
    if (!Array.isArray(this) || !Array.isArray(compareArray) || this.length !== compareArray.length)
      return false;
    var arr1 = this.concat().sort();
    var arr2 = compareArray.concat().sort();
    for (var i = 0; i < arr1.length; i++) {
      if (arr1[i] !== arr2[i])
        return false;
    }
    return true;
  }
}

使用这些

export class TableColumns<T> {
   constructor(private t: new () => T) {
        var fields: string[] = Object.keys(new t())
        console.log('fields', fields)
        console.log('t', t)
    }
}
使用

columns_logs = new TableColumns<LogItem>(LogItem);

输出
fields (12) ["id", "code", "source", "title", "deleted", "checked", "body", "json", "dt_insert", "dt_checked", "screenshot", "uid"]

js类

t class LogItem {
constructor() {
    this.id = 0;
    this.code = 0;
    this.source = '';
    this.title = '';
    this.deleted = false;
    this.checked = false;
  …

这里还有另一个答案也符合作者的要求:'获取接口定义的所有属性名称的方法

如果你在你的类中使用ts-transformer-keys插件和一个接口,你可以得到这个类的所有键。

但是如果你正在使用Angular或React,那么在某些情况下,需要额外的配置(webpack和typescript)才能使其工作:https://github.com/kimamula/ts-transformer-keys/issues/4

到目前为止,答案需要实例化类并依赖Object.Keys。有一种涉及@decorators的方法没有被提及。

在类中,为想要描述的每个字段添加一个装饰器。下面是一个示例,我用SQL列类型注释每个字段。

class Table {
  @field("VARCHAR(30)") lastName?: string;
  @field("TIMESTAMP") dob?: number;
}

然后在类上调用getField返回一个数组,这些字段带有您想要的注释

> getFields(Table) 
 [{ fieldName: "lastName", columnType: "VARCHAR(30)"},
 { fieldName: "dob", columnType: "TIMESTAMP"}]
要做到这一点,您只需要创建两个函数。我在这里做了一个工作示例:https://codepen.io/paganaye/pen/bGvavoL?editors=0010

如果你喜欢,这里是代码

/* Original TypeScript Source 
// ==== getFields method starts here ====
const FIELDS = Symbol("fields");
interface FieldDecorator {
    fieldName: string;
    columnType: string;
}
function field(columnType: string) {
    function createDecoratorFunction(table: any, fieldName: any) {
        let fieldDecorator: FieldDecorator = { fieldName, columnType };
        (table[FIELDS] || (table[FIELDS] = [])).push(fieldDecorator);
        return fieldDecorator as any;
    }
    return createDecoratorFunction;
}
function getFields(table: any): FieldDecorator[] {
    return table.prototype[FIELDS];
}
// ==== getFields method ends here ====
// ==== sample code from here ====
class Table {
    @field("VARCHAR(30)") lastName?: string;
    @field("TIMESTAMP") dob?: number;
}
document.getElementById("output");
output.innerText = JSON.stringify(getFields(Table), undefined, "  ");
*/
"use strict";
// ==== getFields method starts here ====
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);
};
const FIELDS = Symbol("fields");
function field(columnType) {
    function createDecoratorFunction(table, fieldName) {
        let fieldDecorator = { fieldName, columnType };
        (table[FIELDS] || (table[FIELDS] = [])).push(fieldDecorator);
        return fieldDecorator;
    }
    return createDecoratorFunction;
}
function getFields(table) {
    return table.prototype[FIELDS];
}
// ==== getFields method ends here ====
// ==== sample code from here ====
class Table {
}
__decorate([
    field("VARCHAR(30)"),
    __metadata("design:type", String)
], Table.prototype, "lastName", void 0);
__decorate([
    field("TIMESTAMP"),
    __metadata("design:type", Number)
], Table.prototype, "dob", void 0);
document.getElementById("output");
output.innerText = JSON.stringify(getFields(Table), undefined, "  ");
// [
//    { fieldName:"lastName", columnType:"VARCHAR(30)"},
//    { fieldName:"dob",      columnType:"TIMESTAMP"}
// ]
body {
    padding: 2em;
}
pre {
    background-color: #cce;
    padding: 0.5em;
}
<p>Here we start from </p>
<pre>
class Table {
  @field("VARCHAR(30)") lastName?: string;
  @field("TIMESTAMP") dob?: number;
}
</pre>
<p>and we automatically get</p>
<pre id="output">
</pre>
<p>There is no library used you only need this in your tsconfig.json</p>
<pre>
  "compilerOptions": {
    "experimentalDecorators": true
  }
</pre>

我知道没有人喜欢使用this,但这里有一个很酷的例子:

class Account {
    firstName: string;
    lastName: string;
    email: string;
    constructor() {
        console.log(Object.keys(this)); // ['firstName', 'lastName', 'email']
    }
}

相关内容

  • 没有找到相关文章

最新更新