TypeScript系列二
二、TypeScript 基础类型
2.1 Boolean 类型
let isDone: boolean = false;
// ES5: var isDone = false;
2.2 Number 类型
let count: number = 10;
// ES5: var count = 10;
2.3 String 类型
let name: string = "smilker";
// ES5: var string = "smilker"
2.4 Symbol 类型
const sym = Symbol();
let obj = {
[sym]: "smilker"
};
console.log(obj[sym]);
2.5 Array 类型
let list1: number[] = [1, 2, 3];
// ES5: var list = [1, 2, 3];
let list2: Array<number> = [1, 2, 3]; // Array<number> 泛型语法
// ES5: var list = [1, 2, 3]
2.6 Enum 类型
使用枚举我们可以定义一些带名字的常量。
使用枚举可以清晰地表达意图或创建一组区别的用例。
TypeScript 支持数字和基于字符串的枚举。
1. 数字枚举
enum Direction {
NORIH,
SOUTH,
EAST,
WEST
}
let dir: Direction = Direction.NORTH
默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始⾃动增⻓。换句话说Direction.SOUTH的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。
以上的枚举实例经编译后,对应的ES5代码如下:
"use strict";
var Direction; (function (Direction) {
Direction[(Direction["NORTH"] = 0)] = "NORTH";
Direction[(Direction["SOUTH"] = 1)] = "SOUTH";
Direction[(Direction["EAST"] = 2)] = "EAST";
Direction[(Direction["WEST"] = 3)] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;
当然我们也可以设置 NORTH 的初始值,比如:
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST
}
2. 字符串串枚举
在 TypeScript 2.4 版本,允许我们使⽤字符串枚举。在⼀个字符串枚举⾥,每个成员都必须⽤字符串字⾯量,或另外⼀个字符串枚举成员进⾏初始化。
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST"
}
以上代码对应的 ES5 代码如下:
"use strict";
var Direction; (function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
通过观察数字枚举和字符串枚举的编译结果,我们可以知道数字枚举除了⽀持 从成员名称到成员值 的普通映射之外,它还⽀持 从成员值到成员名称 的反向映射:
enum Direction {
NORTH,
SOUTH,
EAST,
WEST
}
let dirName = Direction[0]; // NORTH
let dirVal = Direction["NORTH"]; // 0
另外,对于纯字符串枚举,我们不能省略任何初始化程序。⽽数字枚举如果没有显式设置值时,则会使⽤默认规则进⾏初始化。
3. 常量枚举
除了数字枚举和字符串枚举之外,还有⼀种特殊的枚举 —— 常量枚举。它是使⽤ const 关键字修饰的枚举,常量枚举会使⽤内联语法,不会为枚举类型编译⽣成任何 JavaScript。为了更好地理解这句话,我们来看⼀个具体的例⼦:
const enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
以上代码对应的 ES5 代码如下:
"use strict";
var dir = 0
4. 异构枚举
异构枚举的成员值是数字和字符串的混合:
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F
}
以上代码对于的ES5代码如下:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
通过观察上述⽣成的 ES5 代码,我们可以发现数字枚举相对字符串枚举多了 “反向映射”:
console.log(Enum.A) // 输出: 0
console.log(Enum[0]) // 输出: A
2.7 Any 类型
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型) 。
let notSure: any = 666;
notSure = "semlinker";
notSure = false;
any 类型本质上是类型系统的⼀个逃逸舱。作为开发者,这给了我们很⼤的⾃由:TypeScript 允许我们对 any 类型的值执⾏任何操作,⽽⽆需事先执⾏任何形式的检查。⽐如:
let value: any;
value.foo.bar; // ok
value.trim(); // ok
value(); // ok
new value(); //ok
value[0][1]; // ok
在许多场景下,这太宽松了。使⽤ any 类型,可以很容易地编写类型正确但在运⾏时有问题的代码。如果我们使⽤ any 类型,就⽆法使⽤ TypeScript 提供的⼤量的保护机制。为了解决 any 带来的问题,TypeScript 3.0 引⼊了 unknown 类型。
2.8 Unknown 类型
就像所有类型都可以赋值给 any ,所有类型也都可以赋值给unknown。这使得unknown成为TypeScript类型系统的另一种顶级类型(另一种any)。下面我们来看一下unknown类型的使用示例:
let value: unknown;
value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
对 value 变量的所有赋值都被认为是类型正确的。但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发⽣什么?
let value: unknown;
let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error
unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。现在让我们看看当我们尝试对类型为 unknown 的值执⾏操作时会发⽣什么。以下是我们在之前any 章节看过的相同操作:
let value: unknown;
value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error
将 value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁⽌任何更改。
2.9 Tuple 类型
众所周知,数组⼀般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使⽤元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其⼯作⽅式类似于数组。
元组可⽤于定义具有有限数量的未命名属性的类型。每个属性都有⼀个关联的类型。使⽤元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看⼀个具体的例⼦:
let tupleType: [string, boolean];
tupleType = ["semlinker", true];
在上⾯代码中,我们定义了⼀个名为 tupleType 的变量,它的类型是⼀个类型数组 [string, boolean],然后我们按照正确的类型依次初始化 tupleType 变量。与数组⼀样,我们可以通过下标来访问元组中的元素:
console.log(tupleType[0]); // semlinker
console.log(tupleType[1]); // true
在元组初始化的时候,如果出现类型不匹配的话,比如:
tupleType = [true, "semlinker"];
此时,TypeScript 编译器会提示以下错误信息:
[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.
很明显是因为类型不匹配导致的。在元组初始化的时候,我们还必须提供每个属性的值,不然也会出现错误,⽐如:
tupleType = ["semlinker"];
此时,TypeScript 编译器会提示以下错误信息:
Property '1' is missing in type '[string]' but required in type '[string,
boolean]'.
2.10 Void 类型
某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当⼀个函数没有返回值时,你通常会⻅到其返回值类型是 void:
// 声明函数返回值为 void
function varnUser(): void{
console.log("This is my warning message")
}
以上代码编译⽣成的 ES5 代码如下:
"use strict";
function warnUser() {
console.log("This is my warning message");
}
需要注意的是,声明⼀个 void 类型的变量没有什么作⽤,因为在严格模式下,它的值只能为undefined :
let unuseable: void = undefined;
2.11 Null 和 Undefined 类型
TypeScript ⾥, undefined 和 null 两者有各⾃的类型分别为 undefined 和 null 。
let u: undefined = undefined;
let n: null = null;
2.12 object,Object 和 {} 类型
1. object 类型
object 类型是:TypeScript 2.2 引⼊的新类型,它⽤于表示⾮原始类型。
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor{
create(o: object | null): any;
// ..
}
const proto = {};
Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // Error
Object.create(1337); // Error
Object.create(true); // Error
Object.create("oops"); // Error
2. Object 类型
Object 类型:它是所有 Object 类的实例的类型,它由以下两个接⼝来定义:
- Object 接口定义了 Object.prototype 原型对象上的属性:
// node_modules/typescript/lib/lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
- ObjectConstructor 接口定义了Object类的属性。
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
// ···
}
declare var Object: ObjectConstructor;
Object 类的所有实例都继承了 Object接口中的所有属性。
3. {} 类型
{} 类型描述了⼀个没有成员的对象。当你试图访问这样⼀个对象的任意属性时,TypeScript 会产⽣⼀个编译时错误。
// Type {}
const obj = {};
// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker"
但是,你仍然可以使⽤在 Object 类型上定义的所有属性和⽅法,这些属性和⽅法可通过 JavaScript 的原型链隐式地使⽤:
// Type {}
const obj = {};
// "[object Object]"
obj.toString();
2.13 Never 类型
never 类型表示的是那些永不存在的值的类型。 例如, never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
// 返回never的函数必须存在⽆法达到的终点
function error(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {}
}
在 TypeScript 中,可以利⽤ never 类型的特性来实现全⾯性检查,具体示例如下:
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这⾥ foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这⾥ foo 被收窄为 number 类型
} else {
// foo 在这⾥是 never
const check: never = foo;
}
}
注意在 else 分⽀⾥⾯,我们把收窄为 never 的 foo 赋值给⼀个显示声明的 never 变量。如果⼀切逻辑正确,那么这⾥应该能够编译通过。但是假如后来有⼀天你的同事修改了 Foo 的类型:
type Foo = string | number | boolean;
然⽽他忘记同时修改controlFlowAnalysisWithNever ⽅法中的控制流程,这时候 else 分⽀的 foo类型会被收窄为 boolean 类型,导致⽆法赋值给 never 类型,这时就会产⽣⼀个编译错误。通过这个⽅式,我们可以确保controlFlowAnalysisWithNever ⽅法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出⼀个结论:使⽤ never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码