枚举与类型

枚举是为了让程序可读性更好,比如用来描述用户的角色,普通的会员、付费的会员等,同时也限定了用户角色的种类,保证安全性,不会出现上帝角色这种乱入的东西。

枚举的类别与写法

默认值从0开始,依次递增,这个你应该还记得。

普通的枚举

let str = 'something'

enum test{
    test01,
}

enum FileAccess {
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    Test = test.test01,
    O = str.length
}

对于前面这几种,是常理属性,因为它会直接计算出来,只要是使用了一元运算符号,或者使用了其他枚举的内容,都会计算出来。

则不会,编译出来的 JS 代码会是这个样子。

它这个写法你可能看起来比较复杂。

你分开来看就行了。FileAccess 其实是一个对象。

此时 FileAccess.None 就为 0 了

我们可以看到给属性赋值的时候,返回的是这个值。

接着就是

把编译出来的代码加一个console.log然后放到 html中执行。

所以最后这个对象可能会是这样。

常量枚举

这个枚举只会存在ts文件中,编译成 js不会生成任何代码,全部都用它的值代替。

既然是常量枚举,那就在他前面加个 const 即可

它编译出来之后就这么一句。

var arr = [1 /* A */, 2 /* B */];

直接把它的值给编译了出来,只不过加了一个注释而已。

自动推导

往兼容性最好,描述最准确(且不会报错)的地方推

当我们给 a2 赋值为1的时候,编译器自动推导出为 number

这个被推导成了数字数组,因为number可以接受为 null

这个被推导成了对象数组,因为把 2 付给对象类型是不会报错的,从某种意义上说 2 也是 Number 对象的实例

而当我们给{},添加属性的时候,

它会添加一个|表示或者的关系。

而对于类

你会发现 arr4 被推导成了a[]类型,为什么呢?

因为 ts 判断一个类是否相等是会去看它的类型,而这里的三个类的类型都是{}空对象。所以编译器认为3个都是相等的,所以就用了第一个的名字,也就是 a。

当我们打开 a 的属性的时候,我们发现 arr4 变成了 b[] 类型。

为什么呢?

a 的类型是什么呢?我们用内联的接口描述一下,{a:string}而 b 的类型是{},假如我们 arr4的类型是 a 的类型的数组的话,b 就不符合规范。

arr4的类型是 b 的类型数组,那就全部合适,不会报错。

其实此时我们是已经丢失了类型的。

此时的a,根本就拿不到任何它自己的属性。

跟使用它们的父类去接受值是一样的,丢失了自己的属性。

当然我们可以这样处理,强制转换。

我们把其他的注释也解开

此时的 arr4(a|b|c)[],表示一个可以放a/b/c实例化的数组。

所以我们取到的类型是

依旧还是需要强制转换,那就没有什么其他的办法保证数组里面每一个类的特征吗?

有,利用元组,不过这样就不能把它看做数组,往里面添加新的东西了。

函数参数的自动推导

我们可以看到 此时的 ev 有代码提示,说明已经自动推导出了类型。

选择onkeyupf12,就可以看到以下声明,这是系统自带的类型。

第一个参数是指定 this

这个this就像这样,可以提供代码提示。

这种绑定 this 的做法只能在 ClassInterface 里面使用。

ev就是我们函数的 ev 也就是KeyboardEvent类型,所以才有代码提示。

其实也就是从左边类型,推导出右边的类型。

类型之间的兼容性

之前我们提到过ts自动推导类型的时候,会推导出限制最低的类型。

而判断类型之间是否兼容,会通过类型的比较得出。

这一段代码是正确没有报错的,我们用内联形式描述一下 Person {name:string},恰好与接口Named所描述的是一直的。

也就是说,其实 Person 隐含的意义实现了 Named 接口。

当我们给 Person 添加一个属性的时候依旧没报错。

但是此时的属性 age丢失了,因为 Named上面并没有age

尽管我们知道,它一定有一个 age属性。

而此时为什么可以赋值,其实跟我们之前的数组保证类的特征类似。

此时 Person 的约束为{name:string;age:number}而 Named 的约束为{name:string}

你可以看成解构 {name} = {name,age} 所以此时的 age 就丢失了,尽管在 js 层面是一定存在 age 这个属性的,但是在 ts层面来说,因为你抛弃了age,所以它是不记得有 age这个东西的。

当然假如反过来{name,age} = {name} 就会报错,因为此时 age 拿不到任何值。

也就是说,约束少的可以得到约束多的一部分,也就是俩人都有的属性。

就像类型装换,number 可以转成 string,而并不是所有的 string都可以转成 number,只有存在一点不安全的东西,ts就不允许你这么做。

接下来我们看看函数之间的兼容。

我们可以看到把 x 赋值给 y 并没有报错,而当我们再次调用 y 的时候,依旧需要我们传递2个参数。但是在 js 层面来说,假如我们打印一下 y 的话。

这里为什么可以把一个少参数的函数赋给一个多的参数函数呢?你可能会这样问。

那么我问你,请问函数的参数越多是否意味着约束越强?

答案肯定是的,相比较 x 来说,y 的约束性更强,当把 x 复制给 y 的时候,首先会去判断 x 的参数类型与 y 的参数类型匹配不匹配。

这里的匹配是有顺序的。

当我们增加一个参数,依旧正常。

当我们修改一下位置,立马就报错了。

约束强的可以兼容约束弱的。

通过接口的兼容性来实现来实现 js 的一些特殊的回调方法。

对于 mMouseEventmKeyEvent 来说, mEvent 相当于父类。

listenEvent(eventType: mEventType, handler: (n: mEvent) => void) {} 要求我们第二个参数传入的回调里面的一个参数是 mEvent 类型 因为 mMouseEventmKeyEvent都继承了mEvent,所有传入mMouseEventmKeyEvent 都是可以的。

当然上面的代码也用到了多次强制转换,一个是强制转换回调的类型,一个是强制转换参数的类型。

而对于类来说,只会比较他们之间的实例变量。

当泛型并没有实际约束任何属性的时候,他们是兼容的。

这样就不行了,因为 ts 会去检测属性是否匹配。

类型之间的逻辑

在之前,我们描述一类具有多个特征,可能要通过 implement 来实现多个接口,而现在我们可以&|来实现泛型、类型之间的逻辑。

比如 Person & Serializable & Loggable 意味着同时是 PersonSerializableLoggable

此时我们的 a 变量就具有了 a 接口和 b 接口的所以特征。

当然我们也可以等调用的时候再传

不过这样就不会有任何的代码提示,除非你强制转换。

假如我们的函数想要传递数字或者是字符串,你可能会用 any 不过这样对其他程序员不友好,看函数的类型,并不能理解得到需要具体传啥。

所以我们可以通过|来实现或者逻辑。

而对于接口的逻辑|来说。

这时候我们类型还是Fish | Bird

当我们去调用方法的时候,只能访问到他们共同的方法。

而想要判断是否某一类型,还是需要强转之后判断属性是否存在。

当然我们还可以写一个方法。

这里的代码提示是通过pet is Fish实现的,当然我们可以改成 boolean,尽管不会报错,但是这样我们就不会有代码提示了。

类型别名

你可以认为这种 {new(name:string):Person;hello():void}为内联类型描述,而 interface,认为 class 而类型别名,就好像把多个 class 或者内联的组合在一起。

当然类型别名是不再支持继承的,当你没法用接口描述类型的时候,你就应该尝试这种方式了。

当然,你也可以给出具体的值,这样就有点类似枚举,也就是说,传入的值,必须是其中的某一项。

而对于类型来说,还有一种特殊的类型 this,这是为方便实现实现链式调用。

就像这个 add 方法。

实现链式调用的密码就是返回 this

Last updated