【问题标题】:Get properties of a class获取类的属性
【发布时间】:2018-09-02 04:07:09
【问题描述】:

有没有办法在 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 reflection


    【解决方案1】:

    这个 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);
    

    【讨论】:

    • @erik_cupa 有些陈述是错误的。主要是打字稿生成的代码。例如:typescriptlang.org/play/… 还有很多。
    • @titusfx typescript playground 将代码编译为 ES5。要获得相同的效果,您必须编译为 ES6+。您可以通过在编译器选项中指定目标来实现它(作为参数或 tsconfig.json 的一部分)。更多信息在这里typescriptlang.org/docs/handbook/compiler-options.html
    • @Erik_Cupal 如果你想编译到 ES6+ (这不是默认的编译器选项,我相信它应该在答案中指定)。
    • 恼人的是我们必须定义一个默认值......尤其是在模型中,这很容易被遗忘,这可能导致难以找到的不当行为:/
    【解决方案2】:

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

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

    在打字稿中

    class A {
        private a1;
        private a2;
    
    
    }
    

    在 Javascript 中生成以下code

    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
       }
    }
    

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

    【讨论】:

    • 不错的技巧!当然,不适用于每个构造函数。想象一下这样的事情:constructor(callback: {getValue: () =&gt; number}) { this.numberValue = callback.getValue(); }
    【解决方案3】:

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

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

    【讨论】:

    • 对象必须存在且属性已填充,否则将无法记录属性。
    • @Zarepheth 感谢您对所需检查的澄清,我已经省略了它们以避免像前面那样在答案中混乱,这个例子只是为了简单地展示如何遍历键
    • 除非我弄错了,否则提问者想在运行时获取类属性列表,可能不考虑是否有该类的实例。我遇到了这个,试图做到这一点,如果我不先实例化一个实例就没有任何成功。
    • @Zarepheth,我明白你现在在说什么了。 Typescript 目前不支持反射到它的类型,我已经澄清了你必须先实例化的答案
    【解决方案4】:

    只是为了好玩

    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"]
    

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

    【讨论】:

    • 是'* = void 0;'诬告?或者我们可以只使用 'private a0:any' ?
    • 你需要初始化属性,生成构造函数代码。
    【解决方案5】:

    我目前正在为 Typescript 开发一个类似于 Linq 的库,并希望在 Typescript / Javascript 中实现 C# 的 GetProperties 之类的东西。我使用 Typescript 和泛型的次数越多,我就越清楚地了解到,您通常必须拥有一个具有初始化属性的实例化对象,才能在运行时获取有关类属性的任何有用信息。但无论如何,最好只从构造函数对象或对象数组中检索信息,并且对此保持灵活。

    这就是我现在的结果。

    首先,我为 C# 开发人员定义了 Array 原型方法(“扩展方法”)。

    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的答案大致相同。它可以处理类 Function 和如果你得到一个对象。如果没有给出类 Function (即 C# 开发人员的类“类型”),它将使用 Object.getOwnPropertyNames。

    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;
      }
    }
    

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

      • 我不知道你为什么被否决 - 我正在寻找的完美答案。谢谢!
      • 这应该是最佳答案
      【解决方案7】:

      使用这些

      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;
        …
      

      【讨论】:

        【解决方案8】:

        这里还有另一个答案也符合作者的要求:'compile-time' way to get all property names defined interface

        如果您使用插件ts-transformer-keys 和您的课程的接口,您可以获得该课程的所有密钥。

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

        【讨论】:

          【解决方案9】:

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

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

          【讨论】:

            猜你喜欢
            • 2021-03-26
            • 2021-07-16
            • 1970-01-01
            • 2013-01-16
            • 2019-09-28
            • 2014-06-27
            • 1970-01-01
            • 2012-07-06
            相关资源
            最近更新 更多