枚舉部分
Enumeration part
使用枚舉我們可以定義一些有名字的數字常量。 枚舉通過 enum關鍵字來定義。
Using enumerations, we can define some numeric constants with names. Enumeration is defined by the enum keyword.
enum Direction {
Up = 1,
Down,
Left,
Right
}
枚舉是在運行時真正存在的一個對象,其中一個原因是因為這樣可以從枚舉值到枚舉名進行反向映射
Enumeration is an object that really exists at runtime, one of the reasons is that it maps backwards from enumeration values to enumeration names.
enum Enum{
A
}
let a=Enum.A;
let nameOfA=Enum[Enum.A];//A
引用枚舉成員總會生成一次屬性訪問並且永遠不會內聯。 在大多數情況下這是很好的並且正確的解決方案。 然而有時候需求卻比較嚴格。
Reference enumeration members always generate an attribute access and never inline. In most cases this is a good and correct
solution. Sometimes, however, demand is more stringent.
當訪問枚舉值時,為了避免生成多余的代碼和間接引用,可以使用常數枚舉。
When accessing enumerated values, constant enumerations can be used to avoid generating redundant code and indirect references.
常數枚舉是在enum關鍵字前使用const修飾符。
Constant enumeration uses const modifiers before enum keywords.
const enum Enum{
A=1,
B=A*2
}
常數枚舉只能使用常數枚舉表達式並且不同於常規的枚舉的是它們在編譯階段會被刪除。
常數枚舉成員在使用的地方被內聯進來。 這是因為常數枚舉不可能有計算成員。
Constant enumerations can only use constant enumeration expressions and, unlike
conventional enumerations, they are deleted during compilation. Constant enumeration
members are inlined where they are used. This is because constant enumerations cannot have computational members.
const enum Directions{
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
外部枚舉
External enumeration
外部枚舉用來描述已經存在的枚舉類型的形狀
External enumerations are used to describe the shape of existing enumeration types
declare enum Enum{
A=1,
B,
C=2
}
外部枚舉和非外部枚舉之間有一個重要的區別,在正常的枚舉里,沒有初始化方法的成員被當成常數成員。
對於非常數的外部枚舉而言,沒有初始化方法時被當做需要經過計算的。
There is an important difference between external and non-external enumerations. In normal enumerations,
members without initialization methods are treated as constant members. For non-constant external
enumerations, the absence of initialization methods is considered computational.
類型推論
type inference
最佳通用類型
Best Universal Type
當需要從幾個表達式中推斷類型時候,會使用這些表達式的類型來推斷出一個最合適的通用類型。
When it is necessary to infer types from several expressions, the types of these expressions
are used to infer the most appropriate generic type.
let x=[0,1,null];
計算通用類型算法會考慮所有的候選類型,並給出一個兼容所有候選類型的類型。
Computing generic type algorithms takes into account all candidate types and
gives a type compatible with all candidate types.
由於最終的通用類型取自候選類型,有些時候候選類型共享相同的通用類型,
但是卻沒有一個類型能做為所有候選類型的類型。例如:
Because the final generic type is taken from the candidate type, sometimes the candidate
type shares the same generic type, but none of them can be used as the type of all candidate types. For example:
let zoo = [new Rhino(), new Elephant(), new Snake()];
我們想讓zoo被推斷為Animal[]類型,但是這個數組里沒有對象是Animal類型的,因此不能推斷出這個結果。
為了更正,當候選類型不能使用的時候我們需要明確的指出類型:
We want zoo to be inferred as an Animal [] type, but no object in this array is an Animal type,
so we can't infer this result. To correct this, we need to specify the type when the candidate type is not available:
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
如果沒有找到最佳通用類型的話,類型推論的結果是空對象類型,{}。
因為這個類型沒有任何成員,所以訪問其成員的時候會報錯。
If the best generic type is not found, the result of type inference is an empty object type, {}.
Because there are no members of this type, there is an error when accessing its members.
上下文類型
Context type
TypeScript類型推論也可能按照相反的方向進行。 這被叫做“按上下文歸類”。
按上下文歸類會發生在表達式的類型與所處的位置相關時。比如:
TypeScript type inferences may also proceed in the opposite direction. This is called
"categorizing by context". Categorization by context occurs when the type of expression
is related to its location. For example:
window.onmousedown=function(mouseEvent){
console.log(mouseEvent.button);
}
TypeScript類型檢查器使用Window.onmousedown函數的類型來推斷右邊函數表達式的類型。
因此,就能推斷出 mouseEvent參數的類型了。 如果函數表達式不是在上下文類型的位置,
mouseEvent參數的類型需要指定為any,這樣也不會報錯了。
The TypeScript type checker uses the type of the Window. onmousedown function to infer the
type of the right-hand function expression. Therefore, the type of mouseEvent parameter can be
inferred. If the function expression is not located in the context type, the type of the mouseEvent
parameter needs to be specified as any, which will not cause an error.
window.onmousedown=function(mouseEvent:any){
console.log(mouseEvent.button);
}
上下文類型也會做為最佳通用類型的候選類型。
Context types are also candidates for the best generic types.
function createZoo():Animal[]{
return [new Rhino(),new Elephant(),new Snake()];
}
類型兼容性
type compatibility
TypeScript里的類型兼容性是基於結構子類型的。 結構類型是一種只使用其成員來描述類型的方式。
它正好與名義(nominal)類型形成對比。
Type compatibility in TypeScript is based on structural subtypes. Structural types are a way of
describing types using only their members. It is in contrast to the nominal type.
interface Named{
name:string;
}
class Person{
name:string;
}
let p:Named;
p=new Person();
因為JavaScript里廣泛地使用匿名對象,例如函數表達式和對象字面量,
所以使用結構類型系統來描述這些類型比使用名義類型系統更好。
Because anonymous objects such as function expressions and object literals are widely used in JavaScript,
it is better to describe these types using a structured type system than using a nominal type system.
關於可靠性的注意事項
Notes on Reliability
interface Named{
name:string;
}
let x:Named;
let y={name:'Alice',location:'Seattle'};
x=y;
``` y必須包含名字是name的string類型成員。y滿足條件,因此賦值正確。
Y must contain a string type member whose name is name. Y satisfies the condition, so the assignment is correct.
```js
function greet(n:Named){
alert('Hello'+n.name);
}
greet(y);
注意,y有個額外的location屬性,但這不會引發錯誤。 只有目標類型(這里是 Named)的成員會被一一檢查是否兼容。
Note that y has an additional location attribute, but this does not cause an error. Only members of the target type
(Named in this case) are checked for compatibility.
這個比較過程是遞歸進行的,檢查每個成員及子成員。
This comparison process is carried out recursively, checking each member and its submembers.
比較兩個函數
Compare two functions
相對來講,在比較原始類型和對象類型的時候是比較容易理解的,問題是如何判斷兩個函數是兼容的。
下面我們從兩個簡單的函數入手,它們僅是參數列表略有不同:
Relatively speaking, it is easier to understand when comparing the original type and object type.
The problem is how to judge the compatibility of the two functions. Let's start with two simple functions,
which are just slightly different from the list of parameters.
let x=(a:number)=>0;
let y=(b:number,s:string)=>0;
y=x;//ok
x=y;//Error
Array#forEach給回調函數傳3個參數:數組元素,索引和整個數組。 盡管如此,傳入一個只使用第一個參數的回調函數也是很有用的:
Array#forEach pass three parameters to the callback function: array element, index and whole array.
Nevertheless, it is useful to pass in a callback function that uses only the first parameter:et items=[1,2,3];
items.forEach((item)=>console.log(item));
下面來看看如何處理返回值類型,創建兩個僅是返回值類型不同的函數:
Let's look at how to deal with return value types and create two functions with only different return value types:
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error because x() lacks a location property
類型系統強制源函數的返回值類型必須是目標函數返回值類型的子類型。
The type system enforces that the return value type of the source function must be a subtype of the return value type of the objective function.
當比較函數參數類型時,只有當源函數參數能夠賦值給目標函數或者反過來時才能賦值成功。 這是不穩定的,
因為調用者可能傳入了一個具有更精確類型信息的函數,但是調用這個傳入的函數的時候卻使用了不是那么精確的類型信息。
When comparing the types of function parameters, only when the source function parameters can be assigned to
the objective function or vice versa can the assignment be successful. This is unstable because the caller may pass
in a function with more precise type information, but the incoming function is called with less precise type information.
實際上,這極少會發生錯誤,並且能夠實現很多JavaScript里的常見模式。
In fact, this is rarely an error, and it implements many common patterns in JavaScript.
enum EventType { Mouse, Keyboard }
interface Event { timestamp: number; }
interface MouseEvent extends Event { x: number; y: number }
interface KeyEvent extends Event { keyCode: number }
function listenEvent(eventType: EventType, handler: (n: Event) => void) {
/* ... */
}
listenEvent(EventType.Mouse, (e: MouseEvent) => console.log(e.x + ',' + e.y));
listenEvent(EventType.Mouse, (e: Event) => console.log((<MouseEvent>e).x + ',' + (<MouseEvent>e).y));
listenEvent(EventType.Mouse, <(e: Event) => void>((e: MouseEvent) => console.log(e.x + ',' + e.y)));
listenEvent(EventType.Mouse, (e: number) => console.log(e));
可選參數及剩余參數
Optional and residual parameters
當一個函數有剩余參數時,它被當做無限個可選參數。
When a function has residual parameters, it is treated as an infinite number of optional parameters.
這對於類型系統來說是不穩定的,但從運行時的角度來看,可選參數一般來說是不強制的,
因為對於大多數函數來說相當於傳遞了一些undefinded。
This is unstable for type systems, but from a runtime point of view, optional parameters are generally
not mandatory, because for most functions they are equivalent to passing some undefined.
常見的函數接收一個回調函數並用對於程序員來說是可預知的參數但對類型系統來說是不確定的參數來調用:
Common functions receive a callback function and call it with parameters that are predictable for
programmers but uncertain for type systems:
function invokeLater(args:any[],callback:(...args:any[])=>void){}
invokeLater([1,2],(x,y)=>console.log(x+','+y));
// Confusing (x and y are actually required) and undiscoverable
invokeLater([1, 2], (x?, y?) => console.log(x + ', ' + y));
枚舉
enumeration
枚舉類型與數字類型兼容,並且數字類型與枚舉類型兼容。不同枚舉類型之間是不兼容的。比如,
Enumeration types are compatible with numeric types, and numeric types are compatible
with enumeration types. Different enumeration types are incompatible. For example,
enum Status {Ready,Waiting};
enum Color {Red,Blue,Green};
let status=Status.Ready;
status=Color.Green;
類
class
類與對象字面量和接口差不多,但有一點不同:類有靜態部分和實例部分的類型。
比較兩個類類型的對象時,只有實例的成員會被比較。 靜態成員和構造函數不在比較的范圍內。
Classes are similar to object literals and interfaces, but differ in one respect: classes have types of static and instance parts.
When comparing objects of two class types, only the members of the instance are compared. Static members and
constructors are not within the scope of comparison.
class Animal{
feet:number;
constructor(name:string,numFeet:number){}
}
class Size{
feet:number;
constructor(numFeet:number){}
}
let a:Animal;
let s:Size;
a=s;//ok
s=a;//ok
類的私有成員
Private members of classes
私有成員會影響兼容性判斷。 當類的實例用來檢查兼容時,如果它包含一個私有成員,那么目標類型必須包含來自同一個類的這個私有成員。
這允許子類賦值給父類,但是不能賦值給其它有同樣類型的類。
Private members influence compatibility judgments. When an instance of a class is used to check compatibility,
if it contains a private member, the target type must contain the private member from the same class.
This allows subclasses to be assigned to parent classes, but cannot be assigned to other classes of the same type.
泛型
generic paradigm
因為TypeScript是結構性的類型系統,類型參數只影響使用其做為類型一部分的結果類型。比如,
Because TypeScript is a structured type system, type parameters only affect the type of result
that is used as part of the type. For example,
interface Empty<T>{}
let x:Empty<number>;
let y:Empty<string>;
x=y;//
對於沒指定泛型類型的泛型參數時,會把所有泛型參數當成any比較。 然后用結果類型進行比較,就像上面第一個例子。
When generic parameters of a generic type are not specified, all generic parameters are compared as any.
Then compare with the result type, as in the first example above.
let identity=function<T>(x:T):T{}
let reverse=function<U>(y:U):U{}
identity=reverse;
在TypeScript里,有兩種類型的兼容性:子類型與賦值。 它們的不同點在於,賦值擴展了子類型兼容,
允許給 any賦值或從any取值和允許數字賦值給枚舉類型或枚舉類型賦值給數字。
In TypeScript, there are two types of compatibility: subtypes and assignments. The difference between t
hem is that assignment extends subtype compatibility, allows assignment to any or from any and allows
number assignment to enumeration type or enumeration type assignment to numbers.
語言里的不同地方分別使用了它們之中的機制。 實際上,類型兼容性是由賦值兼容性來
控制的甚至在implements和extends語句里。
Different parts of the language use different mechanisms. In fact, type compatibility is controlled
by assignment compatibility even in implements and extends statements.
高級類型
Advanced type
交叉類型(Intersection Types)
我們大多是在混入(mixins)或其它不適合典型面向對象模型的地方看到交叉類型的使用。
(在JavaScript里發生這種情況的場合很多!) 下面是如何創建混入的一個簡單例子:
Most of us see the use of cross-types in mixins or other places that are not suitable for
typical object-oriented models. (There are many situations where this happens in JavaScript!)
Here's a simple example of how to create a mix-in:
function extend<T,U>(first:T,second:U):T&U{
let result=<T&U>{};
for(let id in first){
(<any>result)[id]=(<any>first)[id];
}
for(let id in second){
if(!result.hasOwnProperty(id)){
(<any>result)[id]=(<any>second)[id];
}
}
return result;
}
class Person{
constructor(public name:string){}
}
interface Loggable{
log():void;
}
class ConsoleLogger implements Loggable{
log(){}
}
var jim=extend(new Person("Jim"),new ConsoleLogger());
var n=jim.name;
jim.log();
聯合類型
Joint type
聯合類型與交叉類型很有關聯,但是使用上卻完全不同。 偶爾你會遇到這種情況,
一個代碼庫希望傳入 number或string類型的參數。 例如下面的函數
Joint types are closely related to cross types, but they are quite different in use.
Occasionally you will encounter this situation where a code base wants to pass in parameters
of type number or string. For example, the following functions
function padLeft(value:string,padding:any){
if(typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
padLeft("Hello world",4);//"Hello world"
let indentedString = padLeft("Hello world", true); // 編譯階段通過,運行時報錯
function padLeft(value: string, padding: string | number) {
// ...
}
let indentedString = padLeft("Hello world", true);
如果一個值是聯合類型,我們只能訪問此聯合類型的所有類型里共有的成員。
If a value is a federated type, we can only access members common to all types of the federated type.
interface Bird{
fly();
layEggs();
}
interface Fish{
swim();
layEggs();
}
function getSmallPet():Fish|Bird{
}
let pet=getSmallPet();
pet.layEggs();//ok
pet.swim();//error
類型保護與區分類型
Type Protection and Type Differentiation
聯合類型非常適合這樣的情形,可接收的值有不同的類型。 當我們想明確地知道是否拿到 Fish時會怎么做?
JavaScript里常用來區分2個可能值的方法是檢查它們是否存在。 像之前提到的,我們只能訪問聯合類型的所有類型中共有的成員。
Joint types are well suited for this situation, and the values that can be received are of different types. What do we do
when we want to know exactly if we get Fish? The common way to distinguish two possible values in JavaScript is to check
whether they exist. As mentioned earlier, we can only access members common to all types of Federation types.
let pet = getSmallPet();
每一個成員訪問都會報錯
Every member will report an error when visiting
if (pet.swim) {
pet.swim();
}
else if (pet.fly) {
pet.fly();
}
為了讓這段代碼工作,我們要使用類型斷言:
To make this code work, we use type assertions:
let pet=getSmallPet();
if((<Fish>pet).swim){
(<Fish>pet).swim();
}else{
(<Bird>pet).fly();
}
用戶自定義的類型保護
User-defined type protection
類型保護就是一些表達式,它們會在運行時檢查以確保在某個作用域里的類型。
要定義一個類型保護,我們只要簡單地定義一個函數,它的返回值是一個 類型斷言:
Type protection is expressions that are checked at runtime to ensure types in a scope.
To define a type protection, we simply define a function whose return value is a type assertion:
function isFish(pet:Fish|Bird):pet is Fish{
return(<Fish>pet).swim!==undefined;
}
在這個例子里,pet is Fish就是類型斷言。 一個斷言是 parameterName is Type這種形式,
parameterName必須是來自於當前函數簽名里的一個參數名。
In this case, pet is Fish is a type assertion. An assertion is in the form of parameterName is Type,
which must be a parameter name from the current function signature.
每當使用一些變量調用isFish時,TypeScript會將變量縮減為那個具體的類型,只要這個類型與變量的原始類型是兼容的。
Whenever isFish is called with some variables, TypeScript reduces the variable to that specific type, as long
as it is compatible with the original type of the variable.
'swim' 和 'fly' 調用都沒有問題了
'swim'and'fly' calls are fine
if (isFish(pet)) {
pet.swim();
}
else {
pet.fly();
}
typeof類型保護
Typeof type protection
function isNumber(x:any):x is number{
return typeof x==="number";
}
function isString(x:any):x is string{
return typeof x==="string";
}
function padLeft(value:string,padding:string|number){
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
現在我們不必將 typeof x === "number"抽象成一個函數,因為TypeScript可以將它識別為一個類型保護。
也就是說我們可以直接在代碼里檢查類型了。
Now we don't have to abstract typeof x == "number" into a function, because TypeScript can recognize
it as a type protection. That is to say, we can check the type directly in the code.
function padLeft(value:string,padding:string|number){
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
instanceof類型保護
Instanceof type protection
instanceof類型保護是通過構造函數來細化類型的一種方式.
Instance of type protection is a way to refine types by constructors.
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
類型為SpaceRepeatingPadder | StringPadder
Type is SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 類型細化為'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 類型細化為'StringPadder'
}
instanceof的右側要求是一個構造函數,TypeScript將細化為:
The right side of instanceof requires a constructor, and TypeScript refines it to:
此構造函數的prototype屬性的類型,如果它的類型不為any的話
The type of the prototype attribute of this constructor if its type is not any
構造簽名所返回的類型的聯合
Construct a union of the types returned by the signature
類型別名
Type alias
類型別名會給一個類型起個新名字。 類型別名有時和接口很像,但是可以作用於原始值,聯合類型,
元組以及其它任何你需要手寫的類型。
Type aliases give a new name to a type. Type aliases are sometimes similar to interfaces, but they
can work on primitive values, federated types, tuples, and any other type you need to write by hand.
type Name=string;
type NameResolver=()=>string;
type NameOrResolver=Name|NameResolver;
function getName(n:NameOrResolver):Name{
if (typeof n === 'string') {
return n;
}
else {
return n();
}
}
起別名不會新建一個類型 - 它創建了一個新名字來引用那個類型。 給原始類型起別名通常沒什么用,
盡管可以做為文檔的一種形式使用。
Type - It creates a new name to refer to that type. Aliases for primitive types are usually useless,
although they can be used as a form of documentation.
同接口一樣,類型別名也可以是泛型 - 我們可以添加類型參數並且在別名聲明的右側傳入:
Like interfaces, type aliases can also be generic - we can add type parameters and pass in on the right side of the alias declaration:
type Container<T>={value:T};
type Tree<T>={
value:T;
left:Tree<T>;
right:Tree<T>;
}
type LinkedList<T>=T&{next:LinkedList<T>};
interface Person{
name:string;
}
var people:LinkedList<Person>;
var s=people.name;
var s=people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;
接口 vs. 類型別名
Interface vs. Type alias
其一,接口創建了一個新的名字,可以在其它任何地方使用。 類型別名並不創建新名字—比如,錯誤信息就不會使用別名。
First, the interface creates a new name that can be used anywhere else. Type aliases do
not create new names -- for example, no aliases are used for error messages.
type Alias={num:number}
interface Interface{
num:number;
}
declare function aliased(arg:Alias):Alias;
declare function interface(arg:Interface):Interface;
另一個重要區別是類型別名不能被extends和implements(自己也不能extends和implements其它類型)。
因為 軟件中的對象應該對於擴展是開放的,但是對於修改是封閉的,你應該盡量去使用接口代替類型別名。
Another important difference is that type aliases cannot be extended and implements (they cannot extend
and implements other types themselves). Because objects in software should be open to extensions, but
closed to modifications, you should try to use interfaces instead of type aliases.
另一方面,如果你無法通過接口來描述一個類型並且需要使用聯合類型或元組類型,這時通常會使用類型別名。
On the other hand, if you can't describe a type through an interface and you need to use a federated
type or tuple type, you usually use a type alias.
字符串字面量類型
String literal type
字符串字面量類型允許你指定字符串必須的固定值。
The string literal type allows you to specify the fixed values that a string must have.
在實際應用中,字符串字面量類型可以與聯合類型,類型保護和類型別名很好的配合。
In practical applications, string literal type can be well matched with joint type,
type protection and type aliases.
type Easing="ease-in"|"ease-out"|"ease-in-out";
class UIElment{
animate(dx:number,dy:number,easing:Easing){
if (easing === "ease-in") {
// ...
}
else if (easing === "ease-out") {
}
else if (easing === "ease-in-out") {
}
else {
// error! should not pass null or undefined.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
字符串字面量類型還可以用於區分函數重載:
String literal types can also be used to distinguish function overloads:
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
// ... code goes here ...
}
可辨識聯合(Discriminated Unions)
你可以合並字符串字面量類型,聯合類型,類型保護和類型別名來創建一個叫做可辨識聯合的高級模式,
它也稱做標簽聯合或代數數據類型。 可辨識聯合在函數式編程很有用處。 一些語言會自動地為你辨識聯合;
而TypeScript則基於已有的JavaScript模式。 它具有4個要素:
You can combine string literal types, associative types, type protection and type aliases to create an advanced
pattern called recognizable associations, which are also called label associations or algebraic data types. Recognizable
associations are useful in functional programming. Some languages automatically identify federations for you; TypeScript
is based on existing JavaScript patterns. It has four elements:
具有普通的字符串字面量屬性—可辨識的特征
It has the common literal property of strings - identifiable features.
一個類型別名包含了那些類型的聯合—聯合
A type alias contains those types of unions - unions.
此屬性上的類型保護。
Type protection on this property.
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
首先我們聲明了將要聯合的接口。 每個接口都有 kind屬性但有不同的字符器字面量類型。
kind屬性稱做可辨識的特征或標簽。 其它的屬性則特定於各個接口。 注意,目前各個接口間是沒有聯系的。
下面我們把它們聯合到一起:
First we declared the interface to be joined. Each interface has kind properties but has different character
literal types. Kind attributes are called identifiable features or tags. Other attributes are specific to each
interface. Note that there is no connection between interfaces at present. Now let's put them together:
現在我們使用可辨識聯合:
Now we use identifiable unions:
function area(s:Shape){
switch(s.kind){
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
完整性檢查
Integrity check
當沒有涵蓋所有可辨識聯合的變化時,我們想讓編譯器可以通知我們。 比如,如果我們添加了 Triangle到Shape,
我們同時還需要更新area:
When all identifiable joint changes are not covered, we want the compiler to notify us. For example, if we add
Triangle to Shape, we also need to update the area:
type Shape = Square | Rectangle | Circle | Triangle;
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
// should error here - we didn't handle case "triangle"
}
兩種方式可以實現。 首先是啟用 --strictNullChecks並且指定一個返回值類型:
Two ways can be achieved. The first is to enable -- strictNullChecks and specify a return value type:
function area(s: Shape): number { // error: returns number | undefined
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
}
}
第二種方法使用never類型,編譯器用它來進行完整性檢查:
The second method uses the never type, which is used by the compiler for integrity checking:
function assertNever(x:never):never{
throw new Error("Unexpected object"+x);
}
function area(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.height * s.width;
case "circle": return Math.PI * s.radius ** 2;
default: return assertNever(s); // error here if there are missing cases
}
}
多態的this類型
This Type of Polymorphism
多態的this類型表示的是某個包含類或接口的子類型。 這被稱做 F-bounded多態性。 它能很容易的表現連貫接口間的繼承,
A polymorphic this type represents a subtype containing a class or interface. This is known as the F-bounded
polymorphism. It can easily represent inheritance between coherent interfaces.
class BasicCalculator{
public constructor(protected value:number=0){}
public currentValue():number{
return this.value;
}
public add(operand:number):this{
this.value+=operand;
return this;
}
public multiply(operand:number):this{
this.value*=operand;
return this;
}
}
let v=new BasicCalculator(2)
.multiply(5)
.add(1)
.currentValue()
由於這個類使用了this類型,你可以繼承它,新的類可以直接使用之前的方法,不需要做任何的改變。
Because this class uses this type, you can inherit it, and the new class can use the previous method directly without any changes.
class ScientificCalculator extends BasicCalculator{
public constructor(value=0){
super(value);
}
public sin(){
this.value=Math.sin(this.value);
return this;
}
}
let v=new ScientificCalculator(2)
.multiply(5)
.sin()
.add(1)
.currentValue();
Symbols
symbol成為了一種新的原生類型
Symbol has become a new primitive type
let sym1=Symbol();
let sym2=Symbol("key");
Symbols是不可改變且唯一的。
Symbols are immutable and unique.
let sym2=Symbol("key");
let sym3=Symbol("key");
sym2===sym3;//false
像字符串一樣,symbols也可以被用做對象屬性的鍵。
Like strings, symbols can also be used as keys to object attributes.
let sym=Symbol();
let obj={
[sym]:"value"
};
console.log(obj[sym]);//"value"
Symbols也可以與計算出的屬性名聲明相結合來聲明對象的屬性和類成員。
Symbols can also be combined with computed attribute name declarations
to declare the attributes and class members of an object.
const getClassNameSymbol=Symbol();
class C{
[getClassNameSymbol](){
return "C";
}
}
let c=new C();
let className=c[getClassNameSymbol]();//"C"