Skip to content

Commit 5ca2ceb

Browse files
committed
docs: finish chapter 4
1 parent 1665116 commit 5ca2ceb

File tree

3 files changed

+67
-34
lines changed

3 files changed

+67
-34
lines changed

chapters.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
- intro.md: 简介
22
- basic.md: 基本用法
33
- any.md: any 类型,unknown 类型,never 类型
4+
- types.md: 类型系统

docs/any.md

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,6 @@ y.toFixed() // 正确
8383

8484
污染其他具有正确类型的变量,把错误留到运行时,这就是不宜使用`any`类型的另一个主要原因。
8585

86-
### 顶层类型
87-
88-
前面说过,`any`类型可以被赋值为任何类型的值。在 TypeScript 语言中,如果类型`A`可以被赋值为类型`B`,就表示类型`B`具有类型`A`的全部特征,这时就称类型`B`是类型`A`的超类。
89-
90-
那么类型`A`称为父类型,类型`B`称为子类型。TypeScript 的一个规则是,凡是可以使用父类型的地方,都可以使用子类型。
91-
92-
由于任何值都可以赋值给`any`类型,所以`any`类型是 TypeScript 所有其他类型的父类型,或者说,所有其他类型都是`any`的子类型。
93-
9486
## unknown 类型
9587

9688
为了解决`any`类型“污染”其他变量的问题,TypeScript 3.0 引入了[`unknown`类型](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type)。它与`any`含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像`any`那样自由,可以视为严格版的`any`
@@ -148,7 +140,7 @@ a === 1 // 正确
148140

149141
那么,怎么才能使用`unknown`类型变量呢?
150142

151-
答案是只有经过“类型细化”(refine)`unknown`类型变量才可以使用。所谓“类型细化”,就是缩小`unknown`变量的类型范围,确保不会出错。
143+
答案是只有经过“类型缩小”`unknown`类型变量才可以使用。所谓“类型缩小”,就是缩小`unknown`变量的类型范围,确保不会出错。
152144

153145
```typescript
154146
let a:unknown = 1;
@@ -158,7 +150,7 @@ if (typeof a === 'number') {
158150
}
159151
```
160152

161-
上面示例中,`unknown`类型的变量`a`经过`typeof`运算以后,能够确定实际类型是`number`,就能用于加法运算了。这就是“类型细化”,即将一个不确定的类型细化为更明确的类型
153+
上面示例中,`unknown`类型的变量`a`经过`typeof`运算以后,能够确定实际类型是`number`,就能用于加法运算了。这就是“类型缩小”,即将一个不确定的类型缩小为更明确的类型
162154

163155
下面是另一个例子。
164156

@@ -172,7 +164,7 @@ if (typeof s === 'string') {
172164

173165
上面示例中,确定变量`s`的类型为字符串以后,才能调用它的`length`属性。
174166

175-
这样设计的目的是,只有明确`unknown`变量的实际类型,才允许使用它,防止像`any`那样可以随意乱用,“污染”其他变量。类型细化以后再使用,就不会报错。
167+
这样设计的目的是,只有明确`unknown`变量的实际类型,才允许使用它,防止像`any`那样可以随意乱用,“污染”其他变量。类型缩小以后再使用,就不会报错。
176168

177169
总之,`unknown`可以看作是更安全的`any`。一般来说,凡是需要设为`any`类型的地方,通常都应该优先考虑设为`unknown`类型。
178170

docs/types.md

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# TypeScript 的类型系统
22

3-
TypeScript 首先继承了 JavaScript 的类型,在这个基础上,发展出自己的类型系统。
3+
本章是 TypeScript 类型系统的总体介绍。
4+
5+
TypeScript 继承了 JavaScript 的类型,在这个基础上,定义了一套自己的类型系统。
46

57
## 基本类型
68

@@ -19,17 +21,17 @@ JavaScript 语言(注意,不是 TypeScript)将值分成8种类型。
1921

2022
TypeScript 继承了 JavaScript 的类型设计,以上8种类型可以看作 TypeScript 的基本类型。
2123

22-
注意,上面所有类型的名称都是小写字母,首字母大写的`Number``String``Boolean`等都是 JavaScript 语言内置的对象,而不是类型名称。
24+
注意,上面所有类型的名称都是小写字母,首字母大写的`Number``String``Boolean`等在 JavaScript 语言中都是内置对象,而不是类型名称。
2325

2426
另外,undefined 和 null 既可以作为值,也可以作为类型,取决于在哪里使用它们。
2527

26-
这8种基本类型作为 TypeScript 类型系统的基础,组合起来就可以形成复杂类型
28+
这8种基本类型是 TypeScript 类型系统的基础,复杂类型由它们组合而成
2729

2830
以下是它们的简单介绍。
2931

3032
### boolean 类型
3133

32-
`boolean`类型只有`true``false`两个布尔值。
34+
`boolean`类型只包含`true``false`两个布尔值。
3335

3436
```typescript
3537
const x:boolean = true;
@@ -81,7 +83,7 @@ const y:bigint = 3.14; // 报错
8183

8284
上面示例中,`bigint`类型赋值为整数和小数,都会报错。
8385

84-
注意,bigint 类型是 ES2020 标准引入的。如果使用这个类型,TypeScript 编译的目标 JavaScript 版本不能低于 ES2020(编译参数`--target`不低于`es2020`)。
86+
注意,bigint 类型是 ES2020 标准引入的。如果使用这个类型,TypeScript 编译的目标 JavaScript 版本不能低于 ES2020(即编译参数`target`不低于`es2020`)。
8587

8688
### symbol 类型
8789

@@ -111,7 +113,7 @@ const z:object = (n:number) => n + 1;
111113

112114
undefined 和 null 是两种独立类型,它们各自都只有一个值。
113115

114-
undefined 类型只包含一个值`undefined`,表示未定义(即还给出定义,以后可能会有定义)。
116+
undefined 类型只包含一个值`undefined`,表示未定义(即还未给出定义,以后可能会有定义)。
115117

116118
```typescript
117119
let x:undefined = undefined;
@@ -192,7 +194,7 @@ s.charAt(1) // 'e'
192194

193195
### 包装对象类型与字面量类型
194196

195-
由于包装对象的存在,导致每一种原始类型都有包装对象和字面量两种情况
197+
由于包装对象的存在,导致每一个原始类型的值都有包装对象和字面量两种情况
196198

197199
```javascript
198200
'hello' // 字面量
@@ -303,7 +305,7 @@ obj = 1; // 报错
303305

304306
大多数时候,我们使用对象类型,只希望包含真正的对象,不希望包含原始类型。所以,建议总是使用小写类型`object`,不使用大写类型`Object`
305307

306-
注意,无论是大写的`Object`类型,还是小写的`object`类型,都只能表示 JavaScript 内置的原型对象(即`Object.prototype`),用户自定义的属性都不存在于这两个类型之中
308+
注意,无论是大写的`Object`类型,还是小写的`object`类型,都只包含 JavaScript 内置对象原生的属性和方法,用户自定义的属性和方法都不存在于这两个类型之中
307309

308310
```typescript
309311
const o1:Object = { foo: 0 };
@@ -341,12 +343,12 @@ JavaScript 的行为是,变量如果等于`undefined`就表示还没有赋值
341343

342344
```typescript
343345
const obj:object = undefined;
344-
obj.toString() // 错误,但能通过编译
346+
obj.toString() // 编译不报错,运行就报错
345347
```
346348

347349
上面示例中,变量`obj`等于`undefined`,编译不会报错。但是,实际执行时,调用`obj.toString()`就报错了,因为`undefined`不是对象,没有这个方法。
348350

349-
为了避免这种情况,及早发现错误,TypeScript 提供了一个编译选项`--strictNullChecks`。只要打开这个选项,`undefined``null`就不能赋值给其他类型的变量(除了`any`类型和`unknown`类型)。
351+
为了避免这种情况,及早发现错误,TypeScript 提供了一个编译选项`strictNullChecks`。只要打开这个选项,`undefined``null`就不能赋值给其他类型的变量(除了`any`类型和`unknown`类型)。
350352

351353
下面是 tsc 命令打开这个编译选项的例子。
352354

@@ -372,18 +374,18 @@ age = undefined; // 报错
372374
}
373375
```
374376

375-
打开`--strictNullChecks`以后,`undefined``null`这两种值也不能互相赋值了。
377+
打开`strictNullChecks`以后,`undefined``null`这两种值也不能互相赋值了。
376378

377379
```typescript
378-
// 打开 --strictNullChecks
380+
// 打开 strictNullChecks
379381

380382
let x:undefined = null; // 报错
381383
let y:null = undefined; // 报错
382384
```
383385

384386
上面示例中,`undefined`类型的变量赋值为`null`,或者`null`类型的变量赋值为`undefind`,都会报错。
385387

386-
总之,打开`--strictNullChecks`以后,`undefined``null`只能赋值给自身,或者`any`类型和`unknown`类型的变量。
388+
总之,打开`strictNullChecks`以后,`undefined``null`只能赋值给自身,或者`any`类型和`unknown`类型的变量。
387389

388390
```typescript
389391
let x:any = undefined;
@@ -406,7 +408,7 @@ x = 'world'; // 报错
406408
TypeScript 推断类型时,遇到`const`命令声明的变量,如果代码里面没有注明类型,就会推断该变量是值类型。
407409

408410
```typescript
409-
// x 的类型是 https
411+
// x 的类型是 "https"
410412
const x = 'https';
411413

412414
// y 的类型是 string
@@ -432,9 +434,9 @@ const x = { foo: 1 };
432434
const x:5 = 4 + 1; // 报错
433435
```
434436

435-
上面示例中,等号左侧的类型是数值`5`,等号右侧`4 + 1`的类型,TypeScript 推测为`number`。由于`5``number`的子类型,子类型不能赋值为父类型的值,所以报错了。
437+
上面示例中,等号左侧的类型是数值`5`,等号右侧`4 + 1`的类型,TypeScript 推测为`number`。由于`5``number`的子类型,`number``5`的父类型,父类型不能赋值给子类型,所以报错了(详见本章后文)
436438

437-
但是,反过来是可以的,父类型可以赋值为子类型的值
439+
但是,反过来是可以的,子类型可以赋值给父类型
438440

439441
```typescript
440442
let x:5 = 5;
@@ -444,7 +446,7 @@ x = y; // 报错
444446
y = x; // 正确
445447
```
446448

447-
上面示例中,子类型`x`不能赋值为父类型`y`,但是反过来是可以的。
449+
上面示例中,变量`x`属于子类型,变量`y`属于父类型。`y`不能赋值为子类型`x`,但是反过来是可以的。
448450

449451
如果一定要让子类型可以赋值为父类型的值,就要用到类型断言(详见《类型断言》一章)。
450452

@@ -483,7 +485,7 @@ let rainbowColor:'赤'|'橙'|'黄'|'绿'|'青'|'蓝'|'紫';
483485

484486
上面的示例都是由值类型组成的联合类型,非常清晰地表达了变量的取值范围。其中,`true|false`其实就是布尔类型`boolean`
485487

486-
前面提到,打开编译选项`--strictNullChecks`后,其他类型的变量不能赋值为`undefined``null`。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。
488+
前面提到,打开编译选项`strictNullChecks`后,其他类型的变量不能赋值为`undefined``null`。这时,如果某个变量确实可能包含空值,就可以采用联合类型的写法。
487489

488490
```typescript
489491
let name:string|null;
@@ -504,9 +506,9 @@ let x:
504506
| 'four';
505507
```
506508

507-
上面示例中,联合类型的第一个成员`one`前面,也可以加上竖杠
509+
上面示例中,联合类型的第一个成员`one`前面,加上了竖杠
508510

509-
如果一个变量有多种类型,读取该变量时,往往需要进行“类型缩小”(type narrowing),区分该值到底属于哪一种类型,然后再进一步理
511+
如果一个变量有多种类型,读取该变量时,往往需要进行“类型缩小”(type narrowing),区分该值到底属于哪一种类型,然后再进一步处理
510512

511513
```typescript
512514
function printId(
@@ -518,7 +520,7 @@ function printId(
518520

519521
上面示例中,参数变量`id`可能是数值,也可能是字符串,这时直接对这个变量调用`toUpperCase()`方法会报错,因为这个方法只存在于字符串,不存在于数值。
520522

521-
解决方法就是对参数`id`做一下类型缩小,确定它的类型以后再进行处理。这在 TypeScript 里面叫做“类型缩小”。
523+
解决方法就是对参数`id`做一下类型缩小,确定它的类型以后再进行处理。
522524

523525
```typescript
524526
function printId(
@@ -600,11 +602,11 @@ type Age = number;
600602
let age:Age = 55;
601603
```
602604

603-
上面示例中,`type`命令为`number`类型生成一个别名`Age`。这样就能像使用`number`一样,使用`Age`当作类型
605+
上面示例中,`type`命令为`number`类型定义了一个别名`Age`。这样就能像使用`number`一样,使用`Age`作为类型
604606

605607
别名可以让类型的名字变得更有意义,也能增加代码的可读性,还可以使复杂类型用起来更方便,便于以后修改变量的类型。
606608

607-
别名不允许有重名
609+
别名不允许重名
608610

609611
```typescript
610612
type Color = 'red';
@@ -711,6 +713,15 @@ type T = typeof Date(); // 报错
711713

712714
上面示例会报错,原因是 typeof 的参数不能是一个值的运算式,而`Date()`需要运算才知道结果。
713715

716+
另外,`typeof`命令的参数不能是类型。
717+
718+
```typescript
719+
type Age = number;
720+
type MyAge = typeof Age; // 报错
721+
```
722+
723+
上面示例中,`Age`是一个类型别名,用作`typeof`命令的参数就会报错。
724+
714725
typeof 是一个很重要的 TypeScript 运算符,有些场合不知道某个变量`foo`的类型,这时使用`typeof foo`就可以获得它的类型。
715726

716727
## 块级类型声明
@@ -728,3 +739,32 @@ if (true) {
728739
```
729740

730741
上面示例中,存在两个代码块,其中分别有一个类型`T`的声明。这两个声明都只在自己的代码块内部有效,在代码块外部无效。
742+
743+
## 类型的兼容
744+
745+
TypeScript 的类型存在兼容关系,某些类可以兼容其他类。
746+
747+
```typescript
748+
type T = number|string;
749+
750+
let a:number = 1;
751+
let b:T = a;
752+
```
753+
754+
上面示例中,变量`a``b`的类型是不一样的,但是变量`a`赋值给变量`b`并不会报错。这时,我们就认为,变量`b`兼容变量`a`
755+
756+
TypeScript
757+
为这种情况定义了一个专门术语。如果类型`A`的值可以赋值给类型`B`,那么类型`A`就称为类型`B`的子类型(subtype)。在上例中,类型`number`就是类型`number|string`的子类型。
758+
759+
TypeScript 的一个规则是,凡是可以使用父类型的地方,都可以使用子类型,但是反过来不行。
760+
761+
```typescript
762+
let a:'hi' = 'hi';
763+
let b:string = 'hello';
764+
765+
b = a; // 正确
766+
a = b; // 报错
767+
```
768+
769+
上面示例中,`hi``string`的子类型,`string``hi`的父类型。所以,变量`a`可以赋值给变量`b`,但是反过来就会报错。
770+

0 commit comments

Comments
 (0)