Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8097846

Browse files
committedJul 22, 2023
docs: finish chapter assert
1 parent f3fe894 commit 8097846

File tree

3 files changed

+63
-51
lines changed

3 files changed

+63
-51
lines changed
 

‎chapters.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@
1010
- interface.md: interface
1111
- class.md:
1212
- generics.md: 泛型
13+
- enum.md: Enum 类型
14+
- assert.md: 类型断言

‎docs/assert.md

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ let bar:T = foo; // 报错
1313

1414
上面示例中,最后一行报错,原因是 TypeScript 推断变量`foo`的类型是`string`,而变量`bar`的类型是`'a'|'b'|'c'`,前者是后者的父类型。父类型不能赋值给子类型,所以就报错了。
1515

16-
这时,TypeScript 提供了“类型断言”这样一种手段,允许开发者在代码中“断言”某个值的类型,提示编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。
16+
TypeScript 提供了“类型断言”这样一种手段,允许开发者在代码中“断言”某个值的类型,告诉编译器此处的值是什么类型。TypeScript 一旦发现存在类型断言,就不再对该值进行类型推断,而是直接采用断言给出的类型。
1717

18-
这种做法的实质是,允许开发者在某个位置“绕过”编译器的类型推断,使其能够通过类型检查,避免编译器报错。这样虽然削弱了 TypeScript 类型系统的严格性,但是为开发者带来了方便,毕竟开发者比编译器更了解自己的代码。
18+
这种做法的实质是,允许开发者在某个位置“绕过”编译器的类型推断,让本来通不过类型检查的代码能够通过,避免编译器报错。这样虽然削弱了 TypeScript 类型系统的严格性,但是为开发者带来了方便,毕竟开发者比编译器更了解自己的代码。
1919

2020
回到上面的例子,解决方法就是进行类型断言,在赋值时断言变量`foo`的类型。
2121

@@ -65,7 +65,7 @@ const p:{ x: number } = { x: 0, y: 0 };
6565
// 正确
6666
const p0:{ x: number } =
6767
{ x: 0, y: 0 } as { x: number };
68-
68+
6969
// 正确
7070
const p1:{ x: number } =
7171
{ x: 0, y: 0 } as { x: number; y: number };
@@ -83,7 +83,7 @@ if (username) {
8383
}
8484
```
8585

86-
上面示例中,变量`username`的类型是`HTMLElement|null`,排除了`null`的情况以后,HTMLElement 类型是没有`value`属性的。如果`username`是一个输入框,那么就可以通过类型断言,将它的类型改成`HTMLInputElement`,就可以读取`value`属性。
86+
上面示例中,变量`username`的类型是`HTMLElement | null`,排除了`null`的情况以后,HTMLElement 类型是没有`value`属性的。如果`username`是一个输入框,那么就可以通过类型断言,将它的类型改成`HTMLInputElement`,就可以读取`value`属性。
8787

8888
注意,上例的类型断言的圆括号是必需的,否则`username`会被断言成`HTMLInputElement.value`,从而报错。
8989

@@ -118,12 +118,12 @@ const s2:string = value as string; // 正确
118118

119119
```typescript
120120
const s1:number|string = 'hello';
121-
const s2:number = s1 as number;
121+
const s2:number = s1 as number;
122122
```
123123

124124
上面示例中,变量`s1`是联合类型,可以断言其为联合类型里面的一种具体类型,再将其赋值给变量`s2`
125125

126-
## 类型断言的前提
126+
## 类型断言的条件
127127

128128
类型断言并不意味着,可以把某个值断言为任意类型。
129129

@@ -167,10 +167,10 @@ const m:string = n as unknown as string; // 正确
167167
如果没有声明变量类型,let 命令声明的变量,会被类型推断为 TypeScript 内置的基本类型之一;const 命令声明的变量,则被推断为值类型常量。
168168

169169
```typescript
170-
// 类型推断为 string
170+
// 类型推断为基本类型 string
171171
let s1 = 'JavaScript';
172172

173-
// 类型推断为 JavaScript
173+
// 类型推断为字符串 “JavaScript
174174
const s2 = 'JavaScript';
175175
```
176176

@@ -181,7 +181,7 @@ const s2 = 'JavaScript';
181181
```typescript
182182
let s = 'JavaScript';
183183

184-
type Lang =
184+
type Lang =
185185
|'JavaScript'
186186
|'TypeScript'
187187
|'Python';
@@ -228,7 +228,13 @@ let s = 'JavaScript';
228228
setLang(s as const); // 报错
229229
```
230230

231-
上面示例中,`as const`断言用于变量`s`,就报错了。
231+
上面示例中,`as
232+
const`断言用于变量`s`,就报错了。下面的写法可以更清晰地看出这一点。
233+
234+
```typescript
235+
let s1 = 'JavaScript';
236+
let s2 = s1 as const; // 报错
237+
```
232238

233239
另外,`as const`也不能用于表达式。
234240

@@ -286,7 +292,7 @@ const a2 = [1, 2, 3] as const;
286292
由于`as const`会将数组变成只读元组,所以很适合用于函数的 rest 参数。
287293

288294
```typescript
289-
function add(x: number, y: number) {
295+
function add(x:number, y:number) {
290296
return x + y;
291297
}
292298

@@ -311,8 +317,8 @@ Enum 成员也可以使用`as const`断言。
311317

312318
```typescript
313319
enum Foo {
314-
X,
315-
Y,
320+
X,
321+
Y,
316322
}
317323
let e1 = Foo.X; // Foo
318324
let e2 = Foo.X as const; // Foo.X
@@ -326,16 +332,17 @@ let e2 = Foo.X as const; // Foo.X
326332

327333
```typescript
328334
function f(x?:number|null) {
329-
validateNumber(x);
335+
validateNumber(x); // 自定义函数,确保 x 是数值
330336
console.log(x!.toFixed());
331337
}
332338

333-
function validateNumber(e:number|null) {
334-
// 如果 e 不是数值,就抛出错误
339+
function validateNumber(e?:number|null) {
340+
if (typeof e !== 'number')
341+
throw new Error('Not a number');
335342
}
336343
```
337344

338-
上面示例中,变量`x`的类型是`number|null`,即可能为空。如果为空,就不存在`.toFixed()`方法,编译时会报错。但是,开发者有时可以确认,变量`x`不会为空,这时就可以使用非空断言,为函数体内部的变量`x`加上后缀`!`,编译就不会报错了。
345+
上面示例中,函数`f()`的参数`x`的类型是`number|null`,即可能为空。如果为空,就不存在`x.toFixed()`方法,这样写会报错。但是,开发者可以确认,经过`validateNumber()`的前置检验,变量`x`肯定不会为空,这时就可以使用非空断言,为函数体内部的变量`x`加上后缀`!``x!.toFixed()`编译就不会报错了。
339346

340347
非空断言在实际编程中很有用,有时可以省去一些额外的判断。
341348

@@ -348,21 +355,21 @@ root.addEventListener('click', e => {
348355
});
349356
```
350357

351-
上面示例中,`getElementById()`有可能返回空值`null`,即变量`root`可能为空,这时对它调用`addEventListener()`方法就会报错,通不过编译。但是一般来说,开发者可以确认`root`元素肯定会在网页中存在,这时就可以使用非空断言。
358+
上面示例中,`getElementById()`有可能返回空值`null`,即变量`root`可能为空,这时对它调用`addEventListener()`方法就会报错,通不过编译。但是,开发者如果可以确认`root`元素肯定会在网页中存在,这时就可以使用非空断言。
352359

353360
```typescript
354361
const root = document.getElementById('root')!;
355362
```
356363

357364
上面示例中,`getElementById()`方法加上后缀`!`,表示这个方法肯定返回非空结果。
358365

359-
非空断言会造成安全隐患,只有在确定一个表达式的值不为空时才能使用。比较保险的做法还是手动检查一下是否为空。
366+
不过,非空断言会造成安全隐患,只有在确定一个表达式的值不为空时才能使用。比较保险的做法还是手动检查一下是否为空。
360367

361368
```typescript
362369
const root = document.getElementById('root');
363370

364371
if (root === null) {
365-
throw Error('Unable to find DOM element #root');
372+
throw new Error('Unable to find DOM element #root');
366373
}
367374

368375
root.addEventListener('click', e => {
@@ -387,7 +394,7 @@ class Point {
387394

388395
上面示例中,属性`x``y`会报错,因为 TypeScript 认为它们没有初始化。
389396

390-
这时就可以使用非空断言,表示这两个属性肯定有值,这样就不会报错了。
397+
这时就可以使用非空断言,表示这两个属性肯定会有值,这样就不会报错了。
391398

392399
```typescript
393400
class Point {
@@ -507,17 +514,17 @@ function assertIsDefined<T>(
507514
const assertIsNumber = (
508515
value:unknown
509516
):asserts value is number => {
510-
if (typeof value !== 'number')
511-
throw Error('Not a number');
517+
if (typeof value !== 'number')
518+
throw Error('Not a number');
512519
};
513520

514521
// 写法二
515522
type AssertIsNumber =
516523
(value:unknown) => asserts value is number;
517524

518525
const assertIsNumber:AssertIsNumber = (value) => {
519-
if (typeof value !== 'number')
520-
throw Error('Not a number');
526+
if (typeof value !== 'number')
527+
throw Error('Not a number');
521528
};
522529
```
523530

@@ -568,7 +575,8 @@ function loadPerson(): Person | null {
568575
let person = loadPerson();
569576

570577
function assert(
571-
condition:unknown, message:string
578+
condition: unknown,
579+
message: string
572580
):asserts condition {
573581
if (!condition) throw new Error(message);
574582
}
@@ -585,3 +593,4 @@ console.log(person.name);
585593
- [Const Assertions in Literal Expressions in TypeScript](https://mariusschulz.com/blog/const-assertions-in-literal-expressions-in-typescript), Marius Schulz
586594
- [Assertion Functions in TypeScript](https://mariusschulz.com/blog/assertion-functions-in-typescript), Marius Schulz
587595
- [Assertion functions in TypeScript](https://blog.logrocket.com/assertion-functions-typescript/), Matteo Di Pirro
596+

‎docs/enum.md

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ if (color === BLUE) {/* */}
2020
throw new Error('wrong color');
2121
```
2222

23-
上面示例中,常量`RED``GREEN``BLUE`是相关的,而且它们具体等于什么值并不重要,只要不相等就可以了。
23+
上面示例中,常量`RED``GREEN``BLUE`是相关的,意为变量`color`的三个可能的取值。它们具体等于什么值其实并不重要,只要不相等就可以了。
2424

2525
TypeScript 就设计了 Enum 结构,用来将相关常量放在一个容器里面,方便使用。
2626

@@ -37,7 +37,7 @@ enum Color {
3737
使用时,调用 Enum 的某个成员,与调用对象属性的写法一样,可以使用点运算符,也可以使用方括号运算符。
3838

3939
```typescript
40-
let c = Color.Green; // 1
40+
let c = Color.Green; // 1
4141
// 等同于
4242
let c = Color['Green']; // 1
4343
```
@@ -76,30 +76,30 @@ let Color = {
7676
Enum 结构比较适合的场景是,成员的值不重要,名字更重要,从而增加代码的可读性和可维护性。
7777

7878
```typescript
79-
enum Operator {
80-
ADD,
81-
DIV,
82-
MUL,
79+
enum Operator {
80+
ADD,
81+
DIV,
82+
MUL,
8383
SUB
8484
}
8585

8686
function compute(
8787
op:Operator,
8888
a:number,
8989
b:number
90-
) {
91-
switch (op) {
92-
case Operator.ADD:
90+
) {
91+
switch (op) {
92+
case Operator.ADD:
9393
return a + b;
94-
case Operator.DIV:
95-
return a / b;
94+
case Operator.DIV:
95+
return a / b;
9696
case Operator.MUL:
9797
return a * b;
9898
case Operator.SUB:
99-
return a - b;
99+
return a - b;
100100
default:
101-
throw new Error('wrong operator');
102-
}
101+
throw new Error('wrong operator');
102+
}
103103
}
104104

105105
compute(Operator.ADD, 1, 3) // 4
@@ -110,16 +110,16 @@ compute(Operator.ADD, 1, 3) // 4
110110
Enum 作为类型有一个缺点,就是输入任何数值都不报错。
111111

112112
```typescript
113-
enum Bool {
113+
enum Bool {
114114
No,
115-
Yes
115+
Yes
116116
}
117117

118118
function foo(noYes:Bool) {
119119
// ...
120120
}
121121

122-
func(33); // 不报错
122+
foo(33); // 不报错
123123
```
124124

125125
上面代码中,函数`foo`的参数`noYes`只有两个可用的值,但是输入任意数值,编译都不会报错。
@@ -146,7 +146,7 @@ enum Foo {
146146
B,
147147
C,
148148
}
149-
149+
150150
const Bar = {
151151
A: 0,
152152
B: 1,
@@ -173,7 +173,7 @@ enum Color {
173173
Blue
174174
}
175175

176-
// 等同于
176+
// 等同于
177177
enum Color {
178178
Red = 0,
179179
Green = 1,
@@ -288,13 +288,13 @@ const y = 1 /* Color.Green */;
288288
const z = 2 /* Color.Blue */;
289289
```
290290

291-
上面示例中,由于 Enum 结构前面加了`const`关键字,所以编译产物里面就没有生成对应的对象,而是把所有 Enum 成员出现的场合,都替换成对应的常量。
291+
上面示例中,由于 Enum 结构前面加了`const`关键字,所以编译产物里面就没有生成对应的对象,而是把所有 Enum 成员出现的场合,都替换成对应的常量。
292292

293-
如果希望加上`const`关键词后,运行时还能访问 Enum 结构(即编译后依然将 Enum 转成对象),需要在编译时打开`preserveConstEnums`参数
293+
如果希望加上`const`关键词后,运行时还能访问 Enum 结构(即编译后依然将 Enum 转成对象),需要在编译时打开`preserveConstEnums`编译选项
294294

295295
## 同名 Enum 的合并
296296

297-
多个同名的 Enum 结构合并成一个 Enum 结构
297+
多个同名的 Enum 结构会自动合并
298298

299299
```typescript
300300
enum Foo {
@@ -404,7 +404,7 @@ enum Foo {
404404
}
405405
```
406406

407-
上面示例中,`A`之前没有其他成员,所以可以不设置初始值,默认等于`0``C`之前有一个字符串成员,必须有初始值,不赋值就报错了。
407+
上面示例中,`A`之前没有其他成员,所以可以不设置初始值,默认等于`0``C`之前有一个字符串成员,必须`C`必须有初始值,不赋值就报错了。
408408

409409
Enum 成员可以是字符串和数值混合赋值。
410410

@@ -515,9 +515,9 @@ type Foo = keyof typeof MyEnum;
515515
注意,这里的`typeof`是必需的,否则`keyof MyEnum`相当于`keyof number`
516516

517517
```typescript
518+
type Foo = keyof MyEnum;
518519
// "toString" | "toFixed" | "toExponential" |
519520
// "toPrecision" | "valueOf" | "toLocaleString"
520-
type Foo = keyof MyEnum;
521521
```
522522

523523
上面示例中,类型`Foo`等于类型`number`的所有原生属性名组成的联合类型。
@@ -553,7 +553,7 @@ enum Weekdays {
553553
Sunday
554554
}
555555

556-
console.log(Weekdays[3]) // Wednesday
556+
console.log(Weekdays[3]) // Wednesday
557557
```
558558

559559
上面示例中,Enum 成员`Wednesday`的值等于3,从而可以从成员值`3`取到对应的成员名`Wednesday`,这就叫反向映射。
@@ -603,3 +603,4 @@ var MyEnum;
603603
MyEnum["B"] = "b";
604604
})(MyEnum || (MyEnum = {}));
605605
```
606+

0 commit comments

Comments
 (0)
Please sign in to comment.