写ts也有不短的时间了,先凌乱的整理一下日常容易让人困惑的点吧,后续可能按条理整理一下。

type和interface

在主要使用场景下,type和interface的功能是比较重合的,这也是容易造成人困惑的地方。

都能做

描述对象结构

1
2
3
4
5
6
7
8
9
10
11
12
interface User {
name: string
age: number
[property: string]: any
[index: number]: any
}
type T_User = {
name: string
age: number
[property: string]: any
[index: number]: any
};

描述函数

1
2
3
4
5
6
type T_Callback<T> = (data: T) => void;  
interface Callback<T> {
(data: T): void;
}
let fn1:T_Callback<number> = (data:number) => data
let fn2:Callback<number> = (data:number) => data

通过implements实现接口

type和interface都可以用implements关键字来被class实现

1
2
3
4
5
6
7
//type A<T> = {age: number,fn: (data: T) => void;   }
interface A<T> {age: number,fn: (data: T) => void; }
class CLASS_A implements A<number> {
age: 1
fn: (data: number) =>{}
}
let a:CLASS_A = new CLASS_A()

type可以而interface不行

别名

可以用type定义其它interface/type的别名

1
2
3
type NewNameOfA = A 
type Mystr = string
type S_A_MAP = Map<string, A>;

联合类型

1
2
3
type ALL = A | B 
type StringOrNumber = string | number;
type TEXT = string | { text: string };

interface可以而type不行

重复定义自动扩展

1
2
interface A {name: string}
interface A {age: number}

会自动扩展属性

用extends关键字进行扩展

interface能用extends关键字进行扩展,type只能用&

1
2
3
4
5
6
type T_A = {}
type T_B = {} & T_A //type只能用 &
interface A{}
interface B extends A{} //interface扩展使用extend
interface C extends T_A{} //interface用extend扩展type
type T_C = {} & A //type用&扩展interface

最佳实践

能用interface就用interface,只能用type的场景再用type。
官网的说法是遵守扩展开放修改封闭的原则,使用interface更佳。这里可能是interface对extends
的支持?体会不是很深,后续如果有发现具体场景再补充。
参考 https://www.tslang.cn/docs/handbook/advanced-types.html 中的 接口 vs. 类型别名

any、unknown和never

any在声明和调用时都放弃了类型检查,相当于退化成了js。下面的代码会带来运行时异常:

1
2
3
4
5
let user:any = {
name: "abc",
hi: ()=>{console.log("hi")}
}
user.hello()

unknown在声明时放弃类型检查,但是调用时必须先明确类型,不会带来运行时异常。
通过类型推断明确类型:

1
2
3
4
5
6
7
8
9
10
interface HI {
hi: ()=>void
}
let user: unknown = {
a: 1,
hi:()=>{console.log("hi")}
}
//user.hi() //编译阶段就会异常
let hiUser = user as HI //通过类型断言明确类型
hiUser.hi()

通过类型判断明确:

1
2
3
4
5
6
7
8
9
10
function handleUnknown(data: unknown){
if(typeof data == "string"){
return data.toLowerCase()
}
if(typeof data == "number"){
return data.toFixed(2)
}
}
console.log(handleUnknown("STR"))
console.log(handleUnknown("123.456"))

这样无论输入啥类型都不会翻车

never表示永远不会有的类型,一般用于直接抛出异常的函数,或者含有死循环的函数等场景。

1
2
3
4
function test1():never{
throw new Error()
}
let a = test1()

函数声明

函数声明相关的东西老是忘,这里特别记录一下吧。 ts声明函数类型时,分为普通函数构造函数
普通函数需要关注的就两个东西:参数类型、返回值类型。这里要特别注意的是,只关注类型而不关注参数名。接口和type都可以声明函数类型。
构造函数类型复制得用class,而不能用fn,这个也可以参考下面“类”中的理解。

interface声明函数类型

1
2
3
4
interface FN {
(x: number, y: number): number
}
let add1: FN = (a: number, b: number) => { return a+b }

type声明函数类型

1
2
type FN_T = (x: number, y: number) => number
let add2: FN_T = (a: number, b: number) => { return a+b }

直接写出函数类型

1
let add3:(x: number, y: number) => number = (a: number, b: number) => { return a+b }

构造函数类型

1
2
3
4
5
6
7
interface CONSTRUCTOR_FN {
new (x: number, y: number)
}
let add4:CONSTRUCTOR_FN = class A {
constructor(a:number, b:number){}
}
console.log(new add4(1,3))

与众不同的”类”

构造类型与实例类型

ts的类实际上由两个部分组成:构造函数类型和实例类型。
当我们定义一个类:

1
2
3
class A{
age: number;
}

这里名字A在不同的使用场景下有着不同的含义

  • A作为变量名时,它代表着类A这个对象
  • A作为参数类型或者返回值类型时,它代表着类A的实例类型
  • 当用new A()来实例化一个类A的对象时,它代表着类A的构造函数,它的类型是typeof A
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class A{
    age: number
    }
    // A作为变量,此时A代表 类A 这个对象
    let B = A
    //A用来表示参数类型或者返回值类型,代表 类A 的实例类型
    function test(data?: A): A{
    //用new A来初始化对象,代表类Ade构造函数 constructor A(): A
    let a = new A()
    a.age = 2
    return a
    }
    let a = test()
    console.log(a.age)

ts类的js编译结果分析

ts最终运行时还是需要编译为js的,所以我们需要理解一下ts的class究竟是如何实现的,其编译后长的是啥样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Test{
public uname: string
private _age: number
constructor(name:string, age: number){
this.uname = name
this._age = age
}
public echoName(){
return this.uname
}
private _getAge(){
return this._age
}
}

tsc编译后,得到一个js的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
var Test = /** @class */ (function () {
function Test(name, age) {
this.uname = name;
this._age = age;
}
Test.prototype.echoName = function () {
return this.uname;
};
Test.prototype._getAge = function () {
return this._age;
};
return Test;
}());

可以比较明显的得到几个结论:

  • 类对象本身实际上就是一个构造函数
  • 成员属性用构造函数内部属性表示,类实例之间独立
  • 成员函数用原型对象中的函数来表示,类实例之间共享
  • 运行时实际上丢掉了访问标识符,访问标识符仅在ts静态检查和编译阶段有效

基于上面几个结论,可以想到一些突破ts边界的场景,如

1
2
3
4
5
let test = new Test("tom", 28)
console.log(test.uname)
//console.log(test._age) //静态检查、编译都会报错
let toAny:any = test
console.log(toAny._age) //可以拿到私有变量的值

强行访问私有变量

1
2
3
4
5
6
7
8
let cat = new Test("cat", 18)
let dog = new Test("dog", 19)
console.log(cat.echoName(), dog.echoName())
let catAny:any = cat
catAny.__proto__.echoName = ()=>"pig"
console.log(cat.echoName(), dog.echoName())
//cat dog
//pig pig

通过一个对象改掉某个类所有实例的成员函数

作为参数传递时的类型表示方法

类对象作为参数传递时,用类名约束其类型就可以了。
类作为构造器传入时,则需要用 typeof 类名,获取其构造函数类型。

1
2
3
4
5
6
7
8
9
10
11
12
class A{
age: number
}
function test1(a:A):A{
return a
}
function test2(classA: typeof A): A{
return new classA()
}
let a1 = test1(new A())
let a2 = test2(A)
console.log(a1, a2)

上面的示例中,classA的类型提示是 new () => A,也就是说,test2函数可以接受任意一个返回A对象的构造函数。

ts中所有类的构造函数类型都可以表示为

1
interface Handle { new (...args: any): any}

例如我们想写一个泛型函数,接受各种类型的构造器,返回它们的实例类型,那可以这样写

1
2
3
4
5
6
7
8
interface Handle { new (...args: any): any}
function createA<T extends Handle>(type: T):InstanceType<T>{
return new type()
}
class A{
age: number
}
let a = createA<typeof A>(A)

这里注意两个地方,第一个是泛型T的约束是T extends Handle,表示T必须是一个构造函数类型;
第二个是,因为T是构造函数类型,所以返回的实例类型就必须是InstanceType<T>,如果返回值类型写成T的话,表示的实例类型就不是类型的实例对象,而是类型的构造函数对象。

内置类型操作、类型推断

ts内置了一堆类型操作符,包括类型访问属性、类型内容修改、类型推断等。
ts中extends与infer一起使用可以用来做类型推断,上面提到的InstanceType<T>其实就是一种类型推断,推断出构造函数T所返回的实例类型。
ts内置了一些类型推断表达式,总量也不多,可以列举出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
* 将类型T的所有属性设置为可选,作为一个新的类型
*/

type Partial<T> = {
[P in keyof T]?: T[P];
};

/**
*将类型T的所有属性设置为必选,作为一个新的类型
*/

type Required<T> = {
[P in keyof T]-?: T[P];
};

/**
* 将类型T的所有属性设置只读,作为一个新的类型
*/

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

/**
* 从T中选择一部分属性,这些属性类型K必须是T的属性的一部分,作为一个新的类型
* 用于从T中挑选出一部分在某集合的属性
*/

// interface A{
// name: string
// age: number,
// height: number
// }
// type B = Pick<A, "age" | "height">
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

/**
* Construct a type with the properties of T except for those in type K.
* 构造一个具有T的属性的类型,不包含K类型中的属性。
* 用于从T中挑选出一部分不在某集合的属性,与Pick操作相反
*/

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

/**
* 基于类型A,构造一个任意类型的 key→T的字典类型
* 用于快速构造字典类型
* type C = Record<string | 9, A>
*/

type Record<K extends keyof any, T> = {
[P in K]: T;
};

/**
* Exclude from T those types that are assignable to U
* 如果U包含T,则表示T类型,否则表示never类型
* 用于构造非派生自U的类型
* type D1 = Exclude<A, B> //never
* type D2 = Exclude<B, A> //B
*/

type Exclude<T, U> = T extends U ? never : T;

/**
* Extract from T those types that are assignable to U
* 与Exclude想法,判断继承关系,T一定要是继承自U的
* 用于构造派生自U的类型
*/

type Extract<T, U> = T extends U ? T : never;

/**
* 构造非null和undefined的类型
*/

type NonNullable<T> = T extends null | undefined ? never : T;

/**
* 通过infer推断获取函数的参数类型
*/

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

/**
* 通过infer推断获取构造函数的参数类型
*/

type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;

/**
* 通过infer推断获取函数的返回类型
*/

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

/**
* 通过infer推断获取构造函数的返回类型,即类的实例类型
*/

type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;

/**
* 转大写
*/

type Uppercase<S extends string> = intrinsic;

/**
* 转小写
*/

type Lowercase<S extends string> = intrinsic;

/**
* 首字母大写
*/

type Capitalize<S extends string> = intrinsic;

/**
* 首字母小写
*/

type Uncapitalize<S extends string> = intrinsic;

一个用infer获取元组中数据类型的,很有意思例子:

1
2
3
type ElementOf<T> = T extends Array<infer E> ? E : never;
type TTuple = [string, number];
type ToUnion = ElementOf<TTuple>; // string | number

黑科技写法:

1
2
type TTuple = [string, number];
type Res = TTuple[number]; // string | number

装饰器与元数据

装饰器

首先来理解一下装饰器是什么。简单来说,装饰器是一个在代码运行时应用在类定义过程上函数。可以从这里个关键点来理解:

  • 是什么:装饰器是一个有格式要求的函数
  • 执行时机:在定义类的时候执行
  • 干什么:用来修饰类本身、类的成员变量、方法、方法参数
    基于上面的理解,装饰器只能作用于ts类定义阶段,不能作用于函数或者其它变量上。ts定义的装饰器函数有这么几种:
    1
    2
    3
    4
    declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
    declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
    declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
    declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

类装饰器

1
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

类装饰器是长这样的一个函数,它接收的唯一一个参数是类的构造函数。可以看到,它的返回值是TFunction或者void。在返回void时,这个装饰器的作用可能是做在类的构造函数上定义一些元数据等操作;返回TFunction时,则会用返回的新构造函数替换掉老的,也就是说被装饰的类此时已经变成另一个类了。

1
2
3
const normalClassDecorator:ClassDecorator = (target)=>{
console.log("normalClassDecorator:", target.toString())
}

定义一个普通的类装饰器,用来做给类加元数据等操作

1
2
3
4
5
6
7
8
9
10
const replaceClassDecorator = <T extends new (...args)=>any>(target:T)=>{
console.log("replaceClassDecorator:", target.toString())
return class extends target{
newProp = "xx"
constructor(...args){
super(...args)
this.age = 99
}
}
}

定义一个替换类的类装饰器。这里有两点要注意的是:

  • 如果返回的类不继承被装饰的类,那么返回类和被装饰类的构造函数必须一致
  • 如果返回的类继承被装饰的类,那么要么返回类不重写构造函数,如果要重写,则必须有个仅接收...args:any[]作为参数的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
@normalClassDecorator
@replaceClassDecorator
class Test{
static uname: string
age: number
constructor(...args){
this.age = args[0]
}
public echo(){
console.log("i'm a test")
}
}

多个类装饰器同时使用时,离类名近的先执行,上面的例子就是replaceClassDecorator先执行。

方法装饰器

1
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

方法装饰器接收的三个参数分别是:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字。
  • 成员的属性描述符。

如果方法装饰器返回一个值,它会被用作方法的属性描述符,这时候就可以做一些例如修改descriptor中的value来劫持方法等骚操作。
这里注意,方法装饰器同样可以试用在属性的 gettersetter上,不过要注意这俩只能装饰一个,对第一个生效。

属性装饰器

1
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

属性装饰器接收的参数:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  • 成员的名字

由于属性装饰器没办法在类的对象实例化之前访问到真正的属性值,所以它能做的事情比较有限,一般用于根据成员名字创建一些元数据啥的。
一个用属性装饰器给属性定义元数据的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
class Greeter {
@format("Hello, %s")
greeting: string;

constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
console.log(new Greeter(" tencent").greet())

参数装饰器

1
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

参数装饰器接收的三个参数分别是:

  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
  • 成员的名字
  • 参数在函数参数列表中的索引

一个用参数装饰器+方法装饰器合作,来做参数校验的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const requiredMetadataKey = Symbol("required");
function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
//获取已经存在的必选参数索引数组,没有则取值为空数组
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
//将当前index加入到数组里边去
existingRequiredParameters.push(parameterIndex);
//将不能重复的参数index设置回元数据
Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}
function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
//缓存原始方法
let method = descriptor.value;
//将方法体替换一下,加上参数校验
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
//检查参数是否缺失
if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
throw new Error("Missing required argument.");
}
}
}

return method.apply(this, arguments);
}
}
class Greeter {
greeting: string;

constructor(message: string) {
this.greeting = message;
}

@validate
greet(@required name?: string) {
return "Hello " + name + ", " + this.greeting;
}
}
console.log(new Greeter("tencent").greet("wuhan"))
console.log(new Greeter("tencent").greet()) //抛出运行时异常

应用顺序

多种装饰器共存时,调用顺序会按实例成员、静态成员、构造函数、类的顺序来:

  • 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
  • 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
  • 参数装饰器应用到构造函数。
  • 类装饰器应用到类。

多个相同类型的装饰器执行时,先执行写的离被装饰对象近的。

装饰器工厂

注意到一些文档单独把这个概念拿出来说,其实可以用一句话描述:返回一个装饰器函数的函数。
实际使用时也经常把一个装饰器工厂的调用当做装饰器来用,例如nestjs中的 Get("/xxx/path")

元数据

Reflect Metadata 是 ES7 的一个提案,它主要用来在声明的时候添加和读取元数据。TypeScript 在 1.5+ 的版本已经支持它,支持的方式是通过一个reflect-metadata模块来引入Reflect Metadata特性。
这里列举一下reflect-metadata中提供的api用法吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 为一个类添加N个类装饰器
* @param decorators 类装饰器数组
* @param target 目标类
* @returns 添加装饰器后的目标类
* @remarks 注意:装饰器会按数组反序的顺序添加执行
* @example 示例:
*
* class Example { }
*
* // constructor
* Example = Reflect.decorate(decoratorsArray, Example);
*
*/

function decorate(decorators: ClassDecorator[], target: Function): Function;
1
2
3
4
/**
* 为一个属性/方法添加N个属性/方法装饰器,和类的差不多,不细说了
*/
function decorate(decorators: (PropertyDecorator | MethodDecorator)[], target: Object, propertyKey: string | symbol, attributes?: PropertyDescriptor): PropertyDescriptor;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
/**
* 一个默认的,可以用于类、类成员属性/方法、方法参数的装饰器工厂
* @param metadataKey 元数据项的key.
* @param metadataValue 元数据项的value.
* @returns 返回一个装饰器函数.
* @remarks 注意:如果metadataKey在目标上已经被定义过了,那么再次定义会覆盖老的
* @example
*
* // constructor
* @Reflect.metadata(key, value)
* class Example {
* }
*
* // property (on constructor, TypeScript only)
* class Example {
* @Reflect.metadata(key, value)
* static staticProperty;
* }
*
* // property (on prototype, TypeScript only)
* class Example {
* @Reflect.metadata(key, value)
* property;
* }
*
* // method (on constructor)
* class Example {
* @Reflect.metadata(key, value)
* static staticMethod() { }
* }
*
* // method (on prototype)
* class Example {
* @Reflect.metadata(key, value)
* method() { }
* }
*
*/

function metadata(metadataKey: any, metadataValue: any): {
(target: Function): void;
(target: Object, propertyKey: string | symbol): void;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 在目标上定义一个唯一的元数据项
* @param metadataKey 用于存储和检索元数据的key
* @param metadataValue 元数据的值
* @param target 要定义元数据的目标
* @param propertyKey 重载可选项。要定义元数据的目标上的一个key,用来获得真正要定义元数据的目标
* @example
*
* class Example {
* // property declarations are not part of ES6, though they are valid in TypeScript:
* // static staticProperty;
* // property;
*
* static staticMethod(p) { }
* method(p) { }
* }
*
* // property (on constructor)
* Reflect.defineMetadata("custom:annotation", Number, Example, "staticProperty");
*
* // property (on prototype)
* Reflect.defineMetadata("custom:annotation", Number, Example.prototype, "property");
*
* // method (on constructor)
* Reflect.defineMetadata("custom:annotation", Number, Example, "staticMethod");
*
* // method (on prototype)
* Reflect.defineMetadata("custom:annotation", Number, Example.prototype, "method");
*
* // decorator factory as metadata-producing annotation.
* function MyAnnotation(options): PropertyDecorator {
* return (target, key) => Reflect.defineMetadata("custom:annotation", options, target, key);
* }
*
*/

function defineMetadata(metadataKey: any, metadataValue: any, target: Object, propertyKey: string | symbol): void;
1
2
3
4
/**
* 目标对象或其原型链是否定义了提供的元数据key
*/

function hasMetadata(metadataKey: any, target: Object): boolean;
1
2
3
4
5
6
//目标对象或其原型链是否定义了提供的元数据key
function hasMetadata(metadataKey: any, target: Object): boolean;
function hasMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
//目标对象本身是否定义了提供的元数据key
function hasOwnMetadata(metadataKey: any, target: Object): boolean;
function hasOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取目标对象或其原型链上提供的元数据键的元数据值
* @param metadataKey 元数据的key
* @param target 目标对象
* @returns 如果找到就返回元数据的value,没有则返回undefined
* @example
*
* class Example {
* }
*
* // constructor
* result = Reflect.getMetadata("custom:annotation", Example);
*
*/

function getMetadata(metadataKey: any, target: Object): any;
function getMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
function getOwnMetadata(metadataKey: any, target: Object): any;
function getOwnMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): any;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取在目标对象或其原型链上定义的元数据key
* @param target 目标对象
* @returns 元数据key的数组
* @example
*
* class Example {
* }
*
* // constructor
* result = Reflect.getMetadataKeys(Example);
*
*/
function getMetadataKeys(target: Object): any[];
function getMetadataKeys(target: Object, propertyKey: string | symbol): any[];
function getOwnMetadataKeys(target: Object): any[];
function getOwnMetadataKeys(target: Object, propertyKey: string | symbol): any[];
/**
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 根据key删除一个元数据
* @param metadataKey 要删除的key
* @param target 绑定的对象
* @returns 如果找到并删除了元数据,则返回true,否则返回false
* @example
*
* class Example {
* }
*
* // constructor
* result = Reflect.deleteMetadata("custom:annotation", Example);
*
*/

function deleteMetadata(metadataKey: any, target: Object): boolean;
function deleteMetadata(metadataKey: any, target: Object, propertyKey: string | symbol): boolean;

模块和命名空间

这里知识比较零碎,慢慢补全吧

命名空间

命名空间合并(和命名空间、和类等)

和命名空间合并

1
2
3
4
5
6
7
8
namespace Animals {
export class Zebra { }
}

namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}

和类合并,作用是为类扩展静态属性或者内部类等,注意namespace内的东西必须export出来

1
2
3
4
5
6
7
8
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel { } //为类扩展了个内部类
export PREFIX:string = "xxx" //为类扩展了个静态属性

}

和函数合并,与类合并一样,也是扩展了一些静态属性

1
2
3
4
5
6
7
8
9
10
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}

namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}

console.log(buildLabel("Sam Smith"));

举一反三,枚举类型或者其它别的类型, 都可以用namespace来扩展静态属性。

在声明文件中导出用顶级对象表示的API

@tars/utils的声明文件中的一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export namespace timeProvider {
/** 采用 `Date.now()` 的方式获取时间, 此种方式效率最高 */
export function nowTimestamp (): number

/**
* 当前时间相对于 `oTime` 的时间间隔, 与 `nowTimestamp` 配对使用
* @param oTime 相对时间, 由 `nowTimestamp` 函数返回
* @returns 浮点类型, 时间间隔, 单位毫秒
*/
export function diff (oTime: number): number

/** 获取当前的时间戳, 即机器从启动到当前的时间 `process.hrtime` */
export function dateTimestamp (): Timestamp

/**
* 当前时间相对于 `oTime` 的时间间隔, 与 `dateTimestamp` 配对使用
* @param oTime 相对时间, 由 `dateTimestamp` 函数返回
* @returns 浮点类型, 时间间隔, 单位毫秒
*/
export function dateTimestampDiff (oTime: Timestamp): number
}

☞ 参与评论