翻譯:瘋狂的技術宅
原文: http://2ality.com/2018/04/typ...
本文首發微信公衆號:jingchengyideng
歡迎關注,天天都給你推送新鮮的前端技術文章html
閱讀本文後,你應該可以理解如下代碼的含義:前端
interface Array<T> { concat(...items: Array<T[] | T>): T[]; reduce<U>( callback: (state: U, element: T, index: number, array: T[]) => U, firstState?: U): U; ··· }
若是你認爲這段代碼很是神祕 —— 那麼我贊成你的意見。可是(我但願證實)這些符號仍是相對容易學習的。一旦你能理解它們,就能立刻全面、精確的理解這種代碼,從而無需再去閱讀冗長的英文說明。git
TypeScript 有一個在線運行環境。爲了獲得最全面的信息,你應該在 「Options」 菜單中打開全部選項開關。這至關於在 --strict
模式下運行TypeScript編譯器。程序員
我在用 TypeScript 時老是喜歡打開 --strict
開關設置。沒有它,程序可能會稍微好寫一點,可是你也失去了靜態類型檢查的好處。目前此設置可以開啓如下子設置:es6
--noImplicitAny
:若是 TypeScript 沒法推斷類型,則必須指定它。這主要用於函數和方法的參數:使用此設置,你必須對它們進行註釋。github
--noImplicitThis
:若是 this
的類型不清楚則會給出提示信息。--alwaysStrict
:儘量使用 JavaScript 的嚴格模式。--strictNullChecks
:null
不屬於任何類型(除了它本身的類型,null
),若是它是可接受的值,則必須明確指定。--strictFunctionTypes
:對函數類型更加嚴格的檢查。--strictPropertyInitialization
:若是屬性的值不能是 undefined
,那麼它必須在構造函數中進行初始化。更多信息:TypeScript 手冊中的「編譯器選項」一章。面試
在本文中,咱們把類型看做是一組值的集合。 JavaScript 語言(不是TypeScript!)有7種類型:typescript
undefined
的集合。false
和 true
的集合。全部這些類型都是 dynamic:能夠用在運行時。編程
TypeScript 爲 JavaScript 帶來了額外的層:靜態類型。這些僅在編譯或類型檢查源代碼時存在。每一個存儲位置(變量或屬性)都有一個靜態類型,用於預測其動態值。類型檢查可確保這些預測可以實現。還有不少能夠進行 靜態 檢查(不運行代碼)的東西。例如,若是函數 f(x)
的參數 x
是靜態類型 number
,則函數調用 f('abc')
是非法的,由於參數 'abc'
是錯誤的靜態類型。segmentfault
變量名後的冒號開始 類型註釋:冒號後的類型簽名用來描述變量能夠接受的值。例如以代碼告訴 TypeScript 變量 「x」 只能存儲數字:
let x: number;
你可能想知道用 undefined
去初始化 x
是否是違反了靜態類型。 TypeScript 不會容許這種狀況出現,由於在爲它賦值以前不容許操做 x
。
即便在 TypeScript 中每一個存儲位置都有靜態類型,你也沒必要老是明確的去指定它。 TypeScript 一般能夠對它的類型進行推斷。例如若是你寫下這行代碼:
let x = 123;
而後 TypeScript 會推斷出 x
的靜態類型是 number
。
在類型註釋的冒號後面出現的是所謂的類型表達式。這些範圍從簡單到複雜,並按以下方式建立。
基本類型是有效的類型表達式:
對應 JavaScript 動態類型的靜態類型:
- `undefined`, `null` - `boolean`, `number`, `string` - `symbol` - `object`
undefined
與類型 undefined
(取決於所在的位置)TypeScript 的特定類型:
Array
(從技術上講不是 JS 中的類型)any
(全部值的類型)請注意,「undefined
做爲值「 和 」undefined
做爲類型」 都寫作 undefined
。根據你使用它的位置,被解釋爲值或類型。 null
也是如此。
你能夠經過類型運算符對基本類型進行組合的方式來建立更多的類型表達式,這有點像使用運算符 union(∪
)和intersection(∩
)去合併集合。
下面介紹 TypeScript 提供的一些類型運算符。
數組在 JavaScript 中扮演如下兩個角色(有時是二者的混合):
數組 arr
被用做列表有兩種方法表示 ,其元素都是數字:
let arr: number[] = []; let arr: Array<number> = [];
一般若是存在賦值的話,TypeScript 就能夠推斷變量的類型。在這種狀況下,實際上你必須幫它解決類型問題,由於在使用空數組時,它沒法肯定元素的類型。
稍後咱們將回到尖括號表示法(Array<number>
)。
若是你想在數組中存儲二維座標點,那麼就能夠把這個數組看成元組去用。看上去是這個樣子:
let point: [number, number] = [7, 5];
在這種狀況下,你不須要類型註釋。
另一個例子是 Object.entries(obj)
的返回值:一個帶有一個 [key,value] 對的數組,它用於描述 obj
的每一個屬性。
> Object.entries({a:1, b:2}) [ [ 'a', 1 ], [ 'b', 2 ] ]
Object.entries()
的返回值類型是:
Array<[string, any]>
如下是函數類型的例子:
(num: number) => string
這個類型是一個函數,它接受一個數字類型參數而且返回值爲字符串。在類型註釋中使用這種類型(String
在這裏是個函數)的例子:
const func: (num: number) => string = String;
一樣,咱們通常不會在這裏使用類型註釋,由於 TypeScript 知道 String
的類型,所以能夠推斷出 func
的類型。
如下代碼是一個更實際的例子:
function stringify123(callback: (num: number) => string) { return callback(123); }
因爲咱們使用了函數類型來描述 stringify123()
的參數 callback
,因此TypeScript 拒絕如下函數調用。
f(Number);
但它接受如下函數調用:
f(String);
對函數的全部參數進行註釋是一個很好的作法。你還能夠指定返回值類型(不過 TypeScript 很是擅長去推斷它):
function stringify123(callback: (num: number) => string): string { const num = 123; return callback(num); }
void
void
是函數的特殊返回值類型:它告訴 TypeScript 函數老是返回 undefined
(顯式或隱式):
function f1(): void { return undefined } // OK function f2(): void { } // OK function f3(): void { return 'abc' } // error
標識符後面的問號表示該參數是可選的。例如:
function stringify123(callback?: (num: number) => string) { const num = 123; if (callback) { return callback(num); // (A) } return String(num); }
在 --strict
模式下運行 TypeScript 時,若是事先檢查時發現 callback
沒有被省略,它只容許你在 A 行進行函數調用。
TypeScript支持 ES6 參數默認值:
function createPoint(x=0, y=0) { return [x, y]; }
默認值可使參數可選。一般能夠省略類型註釋,由於 TypeScript 能夠推斷類型。例如它能夠推斷出 x
和 y
都是 number
類型。
若是要添加類型註釋,應該這樣寫:
function createPoint(x:number = 0, y:number = 0) { return [x, y]; }
你還能夠用 ES6 rest operator 進行 TypeScript 參數定義。相應參數的類型必須是數組:
function joinNumbers(...nums: number[]): string { return nums.join('-'); } joinNumbers(1, 2, 3); // '1-2-3'
在JavaScript中,有時候變量會是有幾種類型之中的一種。要描述這些變量,可使用 union types。例如,在下面的代碼中,x
是 null
類型或 number
類型:
let x = null; x = 123;
x
的類型能夠描述爲 null | number
:
let x: null|number = null; x = 123;
類型表達式 s | t
的結果是類型 s
和 t
在集合理論意義上的聯合(正如咱們以前看到的那樣,兩個集合)。
下面讓咱們重寫函數 stringify123()
:此次咱們不但願參數 callback
是可選的。應該老是調用它。若是調用者不想傳入一個函數,則必須顯式傳遞 null
。實現以下。
function stringify123( callback: null | ((num: number) => string)) { const num = 123; if (callback) { // (A) return callback(123); // (B) } return String(num); }
請注意,在行 B 進行函數調用以前,咱們必須再次檢查 callback
是否真的是一個函數(行A)。若是沒有檢查,TypeScript 將會報告錯誤。
undefined|T
類型爲 T
的可選參數和類型爲 undefined|T
的參數很是類似。 (另外對於可選屬性也是如此。)
主要區別在於你能夠省略可選參數:
function f1(x?: number) { } f1(); // OK f1(undefined); // OK f1(123); // OK
But you can’t omit parameters of type
可是你不能省略 undefined|T
類型的參數:
function f2(x: undefined | number) { } f2(); // error f2(undefined); // OK f2(123); // OK
null
和 undefined
一般不包含在類型中在許多編程語言中,null
是全部類型的一部分。例如只要 Java 中的參數類型爲 String
,就能夠傳遞 null
而Java 不會報錯。
相反,在TypeScript中,undefined
和 null
由單獨的不相交類型處理。若是你想使它們生效,必需要有一個類型聯合,如 undefined|string
和 null|string
。
與Arrays相似,對象在 JavaScript 中扮演兩個角色(偶爾混合和/或更加動態):
咱們將在本文章中忽略 object-as-dictionaries。順便說一句,不管如何,map 一般是比字典的更好選擇。
接口描述 objects-as-records 。例如:
interface Point { x: number; y: number; }
TypeScript 類型系統的一大優點在於它的結構上,而不是在命名上。也就是說,接口 Point
可以匹配適當結構的全部對象:
function pointToString(p: Point) { return `(${p.x}, ${p.y})`; } pointToString({x: 5, y: 7}); // '(5, 7)'
相比之下,Java 的標稱類型系統須要類來實現接口。
若是能夠省略屬性,則在其名稱後面加上一個問號:
interface Person { name: string; company?: string; }
接口內還能夠包含方法:
interface Point { x: number; y: number; distance(other: Point): number; }
使用靜態類型,能夠有兩個級別:
同理:
普通變量經過 const
,let
等引入。類型變量經過尖括號( <>
)引入。例如如下代碼包含類型變量 T
,經過 <T>
引入。
interface Stack<T> { push(x: T): void; pop(): T; }
你能夠看到類型參數 T
在 Stack
的主體內出現兩次。所以,該接口能夠直觀地理解以下:
Stack
是一堆值,它們都具備給定的類型 T
。每當你提到 Stack
時,必須寫 T
。接下來咱們會看到究竟該怎麼用。.push()
接受類型爲 T
的值。.pop()
返回類型爲 T
的值。若是使用 Stack
,則必須爲 T
指定一個類型。如下代碼顯示了一個虛擬棧,其惟一目的是匹配接口。
const dummyStack: Stack<number> = { push(x: number) {}, pop() { return 123 }, };
map 在 TypeScript 中的定義。例如:
const myMap: Map<boolean,string> = new Map([ [false, 'no'], [true, 'yes'], ]);
函數(和方法)也能夠引入類型變量:
function id<T>(x: T): T { return x; }
你能夠按如下方式使用此功能。
id<number>(123);
因爲類型推斷,還能夠省略類型參數:
id(123);
函數能夠將其她的類型參數傳給接口、類等:
function fillArray<T>(len: number, elem: T) { return new Array<T>(len).fill(elem); }
類型變量 T
在這段代碼中出現三次:
fillArray<T>
:引入類型變量elem:T
:使用類型變量,從參數中選擇它。Array<T>
:將 T
傳遞給 Array
的構造函數。這意味着:咱們沒必要顯式指定Array<T>
的類型 T
—— 它是從參數 elem
中推斷出來的:
const arr = fillArray(3, '*'); // Inferred type: string[]
讓咱們用前面學到的知識來理解最開始看到的那段代碼:
interface Array<T> { concat(...items: Array<T[] | T>): T[]; reduce<U>( callback: (state: U, element: T, index: number, array: T[]) => U, firstState?: U): U; ··· }
這是一個Array的接口,其元素類型爲 T
,每當使用這個接口時必須填寫它:
.concat()
有零個或多個參數(經過 rest 運算符定義)。其中每個參數中都具備類型 T[]|T
。也就是說,它是一個 T
類型的數組或是一個 T
值。方法.reduce()
引入了本身的類型變量 U
。 U
表示如下實體都具備相同的類型(你不須要指定,它是自動推斷的):
state
of callback()
(which is a function)state
是 callback()
的參數(這是一個函數)
callback()
callback()
的返回.reduce()
的可選參數 firstState
.reduce()
.reduce()
的返回callback
還將得到一個 element
參數,其類型與 Array 元素具備相同的類型 T
,參數 index
是一個數字,參數 array
是 T
的值。