我目前正在为 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;
}
}