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的類型兼容性就是基於結構子類型的。下面的例子:this
interface IName {
name: string;
}
class Man {
name: string;
constructor() {
this.name = "鳴人";
}
}
let p: IName;
p = new Man();
p.name;
複製代碼
上面的代碼在TypeScript不會出錯,可是在java等語言中就會報錯,由於Man類沒有明確的說明實現了IName 接口。可能有人會感受上面的例子體現不了什麼,那咱們接下來看下面的不兼容的例子:spa
let man: string = "佐助";
let age: number = 20;
man = age; // 錯誤,類型number和類型string不匹配
age = man; // 錯誤,類型string和類型number不匹配
複製代碼
再看個兼容的例子:
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的泛型中,若是類型參數沒有被成員使用時,對兼容性沒有影響;若是參數被成員使用,則會影響兼容性。
文中有些地方可能會加入一些本身的理解,如有不許確或錯誤的地方,歡迎指出~