Skip to content

Commit 01a20bf

Browse files
committed
handbook: split type compatibility
1 parent 82a3060 commit 01a20bf

File tree

2 files changed

+271
-6
lines changed

2 files changed

+271
-6
lines changed

doc/Handbook.md

+10-6
Original file line numberDiff line numberDiff line change
@@ -2533,7 +2533,7 @@ p = new Person();
25332533
25342534
TypeScript的结构性子类型是根据JavaScript代码的通常写法来设计的。因为JavaScript里常用匿名对象像函数表达式或对象字面量,所以用结构性类型系统来描述这些类型比使用正常的类型系统更好。
25352535
2536-
### <a name="11.0"></a>关于稳定性
2536+
### <a name="11.0"></a>关于稳定性的注意事项
25372537
25382538
TypeScript的类型系统允许那些在编译阶段无法否认其安全性的操作。当一个类型系统具有此属性时,被当做是“不稳定”的。TypeScript里允许这种不稳定行为发生的地方是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其背景。
25392539
@@ -2655,11 +2655,11 @@ invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
26552655
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
26562656
```
26572657
2658-
#### <a name="11.2.3"></a>重载的函数
2658+
#### <a name="11.2.3"></a>函数重载
26592659
26602660
对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。这确保了目标函数可以在所有源函数可调用的地方调用。对于特殊的函数重载签名不会用来做兼容性检查。
26612661
2662-
### <a name="11.3"></a>Enums
2662+
### <a name="11.3"></a>枚举
26632663
26642664
枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如,
26652665
@@ -2671,7 +2671,7 @@ var status = Status.Ready;
26712671
status = Color.Green; //error
26722672
```
26732673
2674-
### <a name="11.4"></a>Classes
2674+
### <a name="11.4"></a>
26752675
26762676
类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。比较两个类类型的对象时,只有实例的成员会被比较。静态成员和构造函数不在比较的范围内。
26772677
@@ -2744,9 +2744,13 @@ identity = reverse; // Okay because (x: any)=>any matches (y: any)=>any
27442744
27452745
#### <a name="11.6.1"></a>子类型与赋值
27462746
2747-
目前为止,我们使用了‘兼容性’,它在语言规范里没有定义。在TypeScript里,有两种类型的兼容性:子类型与赋值。它们的不同点在于,赋值扩展了子类型兼容,允许给‘any’赋值或从‘any’取值和允许数字赋值给枚举类型或枚举类型赋值给数字。
2747+
目前为止,我们使用了`兼容性`,它在语言规范里没有定义。
2748+
在TypeScript里,有两种类型的兼容性:子类型与赋值。
2749+
它们的不同点在于,赋值扩展了子类型兼容,允许给`any`赋值或从`any`取值和允许数字赋值给枚举类型或枚举类型赋值给数字。
27482750
2749-
语言里的不同地方分别使用了它们之中的机制。实际上,类型兼容性是由赋值兼容性来控制的甚至在implements和extends语句里。更多信息,请参阅[TypeScript语言规范](http://go.microsoft.com/fwlink/?LinkId=267121).
2751+
语言里的不同地方分别使用了它们之中的机制。
2752+
实际上,类型兼容性是由赋值兼容性来控制的甚至在`implements`和`extends`语句里。
2753+
更多信息,请参阅[TypeScript语言规范](http://go.microsoft.com/fwlink/?LinkId=267121).
27502754
27512755
## <a name="12"></a>书写.d.ts文件
27522756

doc/handbook/Type Compatibility.md

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
# 介绍
2+
3+
TypeScript里的类型兼容性是以结构性子类型来判断的。
4+
结构性类型是完全根据成员关联类型的一种方式。
5+
与正常的类型判断不同。
6+
看下面的例子:
7+
8+
```TypeScript
9+
interface Named {
10+
name: string;
11+
}
12+
13+
class Person {
14+
name: string;
15+
}
16+
17+
var p: Named;
18+
// OK, because of structural typing
19+
p = new Person();
20+
```
21+
22+
在正常使用类型的语言像C#或Java中,这段代码会报错,因为Person类没有明确说明其实现了Named接口。
23+
24+
TypeScript的结构性子类型是根据JavaScript代码的通常写法来设计的。
25+
因为JavaScript里常用匿名对象像函数表达式或对象字面量,所以用结构性类型系统来描述这些类型比使用正常的类型系统更好。
26+
27+
## 关于稳定性的注意事项
28+
29+
TypeScript的类型系统允许那些在编译阶段无法否认其安全性的操作。当一个类型系统具有此属性时,被当做是“不稳定”的。TypeScript里允许这种不稳定行为发生的地方是经过仔细考虑的。通过这篇文章,我们会解释什么时候会发生这种情况和其背景。
30+
31+
# 开始
32+
TypeScript结构化类型系统的基本规则是,如果`x``y`兼容,那么`y`至少具有与`x`相同的属性。比如:
33+
34+
```TypeScript
35+
interface Named {
36+
name: string;
37+
}
38+
39+
var x: Named;
40+
// y's inferred type is { name: string; location: string; }
41+
var y = { name: 'Alice', location: 'Seattle' };
42+
x = y;
43+
```
44+
45+
这里要检查y是否能赋值给`x`,编译器`x`中的每个属性,看是否能在`y`中也找到对应属性。
46+
`在这个例子中,`y`必须包含名字是`name``string`类型成员。`y`满足条件,因此赋值正确。
47+
48+
检查函数参数时使用相同的规则:
49+
50+
```TypeScript
51+
function greet(n: Named) {
52+
alert('Hello, ' + n.name);
53+
}
54+
greet(y); // OK
55+
```
56+
57+
注意,`y`有个额外的`location`属性,但这不会引发错误。
58+
只有目标类型(这里是`Named`)的成员会被一一检查是否兼容。
59+
60+
这个比较过程是递归进行的,检查每个成员及子成员。
61+
62+
# 比较两个函数
63+
64+
比较原始类型和对象类型时是容易理解的,问题是如何判断两个函数是兼容的。
65+
让我们以两个函数开始,它们仅有参数列表不同:
66+
67+
```TypeScript
68+
var x = (a: number) => 0;
69+
var y = (b: number, s: string) => 0;
70+
71+
y = x; // OK
72+
x = y; // Error
73+
```
74+
75+
要查看x是否能赋值给`y`,首先看它们的参数列表。
76+
`x`的每个参数必须能在`y`里找到对应类型的参数。
77+
注意的是参数的名字相同与否无所谓,只看它们的类型。
78+
这里,`x`的每个参数在`y`中都能找到对应的参数,所以允许赋值。
79+
80+
第二个赋值错误,因为`y`有个必需的第二个参数,但是`x`并没有,所以不允许赋值。
81+
82+
你可能会疑惑为什么允许`忽略`参数,像例子`y=x`中那样。
83+
原因是忽略额外的参数在JavaScript里是很常见的。
84+
例如,`Array#forEach`给回调函数传3个参数:数组元素,索引和整个数组。
85+
尽管如此,传入一个只使用第一个参数的回调函数也是很有用的:
86+
87+
```TypeScript
88+
var items = [1, 2, 3];
89+
90+
// Don't force these extra arguments
91+
items.forEach((item, index, array) => console.log(item));
92+
93+
// Should be OK!
94+
items.forEach((item) => console.log(item));
95+
```
96+
97+
下面来看看如何处理返回值类型,创建两个仅是返回值类型不同的函数:
98+
99+
```TypeScript
100+
var x = () => ({name: 'Alice'});
101+
var y = () => ({name: 'Alice', location: 'Seattle'});
102+
103+
x = y; // OK
104+
y = x; // Error because x() lacks a location property
105+
```
106+
107+
类型系统强制源函数的返回值类型必须是目标函数返回值类型的子类型。
108+
109+
## 函数参数双向协变
110+
当比较函数参数类型时,只有源函数参数能够赋值给目标函数或反过来才匹配成功。
111+
这是不稳定的,因为调用者可能会被给予一个函数,它接受一个更确切类型,但是调用函数使用不那么确切的类型。
112+
实际上,这极少会发生错误,并且能够实现很多JavaScript里的常见模式。例如:
113+
114+
```TypeScript
115+
enum EventType { Mouse, Keyboard }
116+
117+
interface Event { timestamp: number; }
118+
interface MouseEvent extends Event { x: number; y: number }
119+
interface KeyEvent extends Event { keyCode: number }
120+
121+
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
122+
/* ... */
123+
}
124+
125+
// Unsound, but useful and common
126+
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
127+
128+
// Undesirable alternatives in presence of soundness
129+
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
130+
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
131+
132+
// Still disallowed (clear error). Type safety enforced for wholly incompatible types
133+
listenEvent(EventType.Mouse, (e: number) => console.log(e));
134+
```
135+
136+
## 可选参数及剩余参数
137+
比较函数兼容性的时候,可选参数与必须参数是可交换的。
138+
原类型上额外的可选参数并不会造成错误,目标类型的可选参数没有对应的参数也不是错误。
139+
140+
当一个函数有剩余参数时,它被当做无限个可选参数。
141+
142+
这对于类型系统来说是不稳定的,但从运行时的角度来看,可选参数一般来说是不强制的,因为对于大多数函数来说相当于传递了一些`undefinded`
143+
144+
有一个好的例子,常见的函数接收一个回调函数并用对于程序员来说是可预知的参数但对类型系统来说是不确定的参数来调用:
145+
146+
```TypeScript
147+
function invokeLater(args: any[], callback: (...args: any[]) => void) {
148+
/* ... Invoke callback with 'args' ... */
149+
}
150+
151+
// Unsound - invokeLater "might" provide any number of arguments
152+
invokeLater([1, 2], (x, y) => console.log(x + ', ' + y));
153+
154+
// Confusing (x and y are actually required) and undiscoverable
155+
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
156+
```
157+
158+
## 函数重载
159+
160+
对于有重载的函数,源函数的每个重载都要在目标函数上找到对应的函数签名。
161+
这确保了目标函数可以在所有源函数可调用的地方调用。
162+
对于特殊的函数重载签名不会用来做兼容性检查。
163+
164+
# 枚举
165+
166+
枚举类型与数字类型兼容,并且数字类型与枚举类型兼容。不同枚举类型之间是不兼容的。比如,
167+
168+
```TypeScript
169+
enum Status { Ready, Waiting };
170+
enum Color { Red, Blue, Green };
171+
172+
var status = Status.Ready;
173+
status = Color.Green; //error
174+
```
175+
176+
#
177+
178+
类与对象字面量和接口差不多,但有一点不同:类有静态部分和实例部分的类型。
179+
比较两个类类型的对象时,只有实例的成员会被比较。
180+
静态成员和构造函数不在比较的范围内。
181+
182+
```TypeScript
183+
class Animal {
184+
feet: number;
185+
constructor(name: string, numFeet: number) { }
186+
}
187+
188+
class Size {
189+
feet: number;
190+
constructor(numFeet: number) { }
191+
}
192+
193+
var a: Animal;
194+
var s: Size;
195+
196+
a = s; //OK
197+
s = a; //OK
198+
```
199+
200+
## 类的私有成员
201+
202+
私有成员会影响兼容性判断。
203+
当类的实例用来检查兼容时,如果它包含一个私有成员,那么目标类型必须包含来自同一个类的这个私有成员。
204+
这允许子类赋值给父类,但是不能赋值给其它有同样类型的类。
205+
206+
# 泛型
207+
208+
因为TypeScript是结构性的类型系统,类型参数只影响使用其做为类型一部分的结果类型。比如,
209+
210+
```TypeScript
211+
interface Empty<T> {
212+
}
213+
var x: Empty<number>;
214+
var y: Empty<string>;
215+
216+
x = y; // okay, y matches structure of x
217+
```
218+
219+
上面代码里,`x``y`是兼容的,因为它们的结构使用类型参数时并没有什么不同。
220+
把这个例子改变一下,增加一个成员,就能看出是如何工作的了:
221+
222+
```TypeScript
223+
interface NotEmpty<T> {
224+
data: T;
225+
}
226+
var x: NotEmpty<number>;
227+
var y: NotEmpty<string>;
228+
229+
x = y; // error, x and y are not compatible
230+
```
231+
232+
在这里,泛型类型在使用时就好比不是一个泛型类型。
233+
234+
对于没指定泛型类型的泛型参数时,会把所有泛型参数当成`any`比较。
235+
然后用结果类型进行比较,就像上面第一个例子。
236+
237+
比如,
238+
239+
```TypeScript
240+
var identity = function<T>(x: T): T {
241+
// ...
242+
}
243+
244+
var reverse = function<U>(y: U): U {
245+
// ...
246+
}
247+
248+
identity = reverse; // Okay because (x: any)=>any matches (y: any)=>any
249+
```
250+
251+
# 高级主题
252+
253+
## 子类型与赋值
254+
255+
目前为止,我们使用了`兼容性`,它在语言规范里没有定义。
256+
在TypeScript里,有两种类型的兼容性:子类型与赋值。
257+
它们的不同点在于,赋值扩展了子类型兼容,允许给`any`赋值或从`any`取值和允许数字赋值给枚举类型或枚举类型赋值给数字。
258+
259+
语言里的不同地方分别使用了它们之中的机制。
260+
实际上,类型兼容性是由赋值兼容性来控制的甚至在implements和extends语句里。
261+
更多信息,请参阅[TypeScript语言规范](http://go.microsoft.com/fwlink/?LinkId=267121).

0 commit comments

Comments
 (0)