TypeScript真香系列的內容將參考中文文檔,可是文中的例子基本不會和文檔中的例子重複,對於一些地方也會深刻研究。另外,文中一些例子的結果都是在代碼沒有錯誤後編譯爲JavaScript獲得的。若是想實際看看TypeScript編譯爲JavaScript的代碼,能夠訪問TypeScript的在線編譯地址,動手操做,印象更加深入。javascript
TypeScript中的類型推論,就是當咱們沒有明確指定變量的類型時,TypeScript能夠自動的推斷出變量的數據類型。java
let a = 3; a = 4; a = "s"; //錯誤,"s"和number類型不匹配
從上面的例子能夠看出,當咱們定義了一個變量a,而後進行賦值,TypeScript就自動給咱們推斷出變量a的類型。當咱們再給變量a賦值爲字符串的時候,就會出現代碼中的錯誤提示。這樣的寫法在JavaScript中是能夠的,可是在TypeScript中給咱們進行了限制。git
let a = { p: "", c: 0 }; a.p = "火影"; a.p = 1; //錯誤,1和string類型不匹配
上面的例子很簡單,可是當咱們定義的變量爲數組這樣比較複雜的類型的時候,TypeScript就會根據其中的成員來推斷出最合適的通用類型:github
let a = [1, 2, null]; a=["s"]; //錯誤,類型"string"和"number | null"不匹配
上面的例子都是通從右到左判出的類型,TypeScript類型推論也可能按相反的方向來推斷,這被叫作「按上下文歸類」,按上下文歸類會發生在表達式的類型與所處的位置相關時。下面的例子是在函數這一節的:typescript
function sum(a: number, b: number){ return a + b; }
咱們沒有指定返回值的類型,可是TypeScript自動從上到下推斷出返回值的類型爲c#
number。 let man = { a: 1, b: "james", play: (s: string) => { return s } } man.play = function (s){ return s + "s" }
TypeScript中的類型兼容性能夠用於肯定一個類型是否能夠賦值給其餘類型。這裏要了解兩個概念:數組
結構類型:一種只使用其成員來描述類型的方式;
名義類型:明確的指出或聲明其類型,如c#,java。
TypeScript的類型兼容性就是基於結構子類型的。下面的例子:函數
interface IName { name: string; } class Man { name: string; constructor() { this.name = "鳴人"; } } let p: IName; p = new Man(); p.name;
上面的代碼在TypeScript不會出錯,可是在java等語言中就會報錯,由於Man類沒有明確的說明實現了IName 接口。可能有人會感受上面的例子體現不了什麼,那咱們接下來看下面的不兼容的例子:this
let man: string = "佐助"; let age: number = 20; man = age; // 錯誤,類型number和類型string不匹配 age = man; // 錯誤,類型string和類型number不匹配
再看個兼容的例子:rest
let man: any = "佐助"; let age: any = 123 man = age; //123
TypeScript結構化類型系統的基本規則是,若是x要兼容y,那麼y至少具備與x相同的屬性。以下面的例子:
interface IName { name: string; } let x: IName; let y = {name: "鳴人", age: 123, hero: true}; x = y; //{name: "鳴人", age: 123, hero: true}
這裏編譯器檢查了x中的每個屬性,看是否在y中也能找到對應的屬性。而上面的 y 符合了 x 兼容的要求,即x兼容y。
interface IName { name: string; age: number } let x: IName; let z = { name: "佐助", cool: true }; x = z; // 錯誤
這裏編譯器在檢查的時候,發現 z 中少了 x 中的"age"這個屬性,因此 x 和 z 是不兼容的。
上面的例子都是一些原始類型或者對象之間的比較,如今咱們看看函數之間是怎麼比較的:
let x = (a: number) => 0; let y = (b: number, c: string) => 0; y = x; x = y; //錯誤
要看x可否賦值給y,先看x和y的參數列表。x的每一個參數都必須在y裏面找到對應類型的參數,只要參數類型相對應,參數名字無所謂。上面例子中x的參數都能在y中找到對應的參數,因此容許賦值,可是反過來,y就不能給x賦值。
雙向協變包含協變和逆變。協變是指子類型兼容父類型,而逆變正好相反。
let man = (arg: string | number) : void => {}; let player = (arg: string) : void => {}; man = player; player = man;
關於可選參數和rest參數的兼容,能夠看下面的例子:
let man = (x: number, y: number) => {}; let work = (x?: number, y?: number) => {}; let play = (...args: number[]) => {}; man = work = play; play = work = man;
關於重載,咱們先看看java中的定義:
在同一個類中,容許存在一個以上的同名函數,只要他們的參數個數或者參數類型不一樣便可。與返回值類型無關,只看參數列表(參數的個數、參數的類型、參數的順序)
在TypeScript中的函數重載和java中的不一樣,TypeScript中的函數重載僅僅是參數類型重載:
function sum(a: number, b: number): number; function sum(a: string, b: string): string; function sum(a: any, b: any) { let result = null; if (typeof a === "string" && typeof b === "string") { result = <string>a + "和" + <string>b + "是好基友"; } else if (typeof a === "number" && typeof b === "number") { result = <number>a + <number>b } return result; } sum("鳴人", "佐助"); sum(1, 1);
對於有重載的函數,源函數的每一個重載都要在目標函數上找到對應的函數簽名,下面這種方式就是錯誤的:
function sum(a: number, b: number): number; // 錯誤,重載簽名與其實現簽名不兼容 function sum(a: string, b: string): string{ return a + b; };
下面這個例子在TypeScript也不能進行重載:
function sum(a: number, b: number): number{ //錯誤,函數重複實現 return a + b; }; function sum(a: any, b: any): any{ //錯誤,函數重複實現 return a + b; };
而後咱們看看返回值類型怎麼比較的,源函數的返回類型必須是目標函數返回值的子類型:
interface IMan { x: string; y: number; } interface IPlayer { x: string; y: number; z: number; } let man = (): IMan => ({ x: "鳴人", y: 0 }); let player = (): IPlayer => ({ x: "佐助", y: 0, z: 0 }); man = player; player = man; //錯誤
從上面能夠看出,player是man的子類型,因此man兼容player。下面這個例子也體現了這一點:
interface IMan { x: string; y: number; } interface IPlayer { a: string; b: number; c: number; } let man = (): IMan => ({ x: "鳴人", y: 0 }); let player = (): IPlayer => ({ a: "佐助", b: 0, c: 0 }); man = player; //錯誤 player = man; //錯誤
枚舉類型和數字類型相互兼容:
enum Man { name, age, } let num = 1; let num2 = 2; let enumNum: Man.name = num; num2 = Man.name;
不一樣枚舉之間是不兼容的:
enum Man { name, age, } enum Player { name, age, } let man: Man.name = Player.name; //錯誤,類型Player.name不能分配給類型 Man.name let player: Player.age = Man.age; //錯誤,類型 Man.name不能分配給類型 Player.name
在TypeScript中,只有實例成員和方法會被比較,靜態成員和構造函數不會被比較。
class Man { name: string; constructor(arg: string,) { this.name = arg; } showName() { return this.name; } } class Player { static age: number; name: string; constructor(arg: string, hero: boolean) { this.name = arg; } showName() { return this.name; } } let man = new Man("佐助"); let player = new Player("鳴人", true); man = player; player = man;
從上面的例子能夠看出,雖然兩個類有着不一樣的構造函數和靜態成員,可是他們有相同的實例成員和方法,因此他們之間是兼容的。
類的私有成員和受保護成員的兼容性的比較規則是同樣的。比較兩個類的時候要分兩種狀況來看,當兩個類是父子類,父類中有私有成員的時候,兩個類是兼容的;當兩個類是同級的類的時候,並且同級類中包含私有或受保護成員時,就不兼容了。看看下面的兩個例子:
父子類:
class Man { private name: string; constructor(arg: string) { this.name = arg; } } class Player extends Man { constructor(arg: string) { super(arg); } } let man = new Man("鳴人"); let player = new Player("佐助"); //Man類和Player類是父子類,因此兩個類是兼容的 man = player; player = man;
同級類:
class Man { private name: string; constructor(arg: string) { this.name = arg; } } class Player { private name: string; constructor(arg: string) { this.name = arg; } } let man = new Man("鳴人"); let player = new Player("佐助"); man = player; // 錯誤,類型Player不能分配給類型Man,類型具備私有屬性name的單獨聲明 player = man; // 錯誤,類型Man不能分配給類型Player,類型具備私有屬性name的單獨聲明
TypeScript泛型的兼容性分兩種狀況,一種是類型參數沒有被成員使用;另外一種是類型參數被成員使用。
咱們先看當類型參數沒有被成員使用時:
interface IMan<T>{ } let man1: IMan<number>; let man2: IMan<string>; man1 = man2; man2 = man1;
當類型參數被成員使用時:
interface IMan<T>{ name: T; } let man1: IMan<number>; let man2: IMan<string>; man1 = man2; //錯誤,IMan<string>不能分配給IMan<number> man2 = man1; //錯誤,IMan<number>不能分配給IMan<string> interface IMan<T>{ name: T; } let man1: IMan<number>; let man2: IMan<number>; man1 = man2; man2 = man1;
在TypeScript的泛型中,若是類型參數沒有被成員使用時,對兼容性沒有影響;若是參數被成員使用,則會影響兼容性。
https://github.com/zhongsp/Ty...
https://github.com/jkchao/typ...
文中有些地方可能會加入一些本身的理解,如有不許確或錯誤的地方,歡迎指出~