一、數據類型
ts的數據類型幾乎與js一致 提供了數字(number)、字符串(string)、結構體(Object)、布爾值等基礎數據類型,另外還提供了枚舉類(enum)
數字(number)
和js中的一樣ts中的數字都是浮點型 除了支持十進制和十六進制的字面量 ts還支持了 es2015 中引入的二進制和八進制表示形式
let hex:number = 0xf00d;//十六進制
let bin:number = 0b1010;//二進制
let oct:number = 0o744;//八進制```
## 字符串(string)
與js的string一致 可以使用雙引號( " ) 單引號( ' ) 反引號( ` ) 皆可表示字符串
```let who: string = "我";// "雙引號
let name: string = '王鋼蛋';// '單引號
let str: string = `${who}是${name}`;// `模板字符串
console.log(str); //輸出 我是王鋼蛋
數組(Array)
ts 的數組和js的數組略有不同 ,ts中有兩種形式可以定義數組
第一種
是在數據類型后面跟上[] 表示有此類型的元素組成的一個數組 這點跟java類似
let arr: string[] = ["蔡徐坤","謝廣坤","王鋼蛋","吳電鰻"];
第二種
方式是使用數組Array的泛型
//在以上兩種形式聲明的數組中所有元素必須都是同一類型 那么如何定義一個可以包含不同類型的 數組呢
let arr2: Array<string | number> = [1,"one"]; //數組元素可以是 string型 亦可為 number型
let arr3: Array<any> = [1,"yes",true,{attr:'ok'}]; //any表示任意類型 ```
## 元組(Tuple)
元組表示一個已知長度和各元素類型的數組,各個元素類型可不相同
比如
```let t1: [number,string]
t1 = [1,"one"];//正常賦值
t1 = ["one",1];//報錯 元素類型與聲明時不一致
t1 = [1,"one",3];//報錯 元組長度不對
枚舉(enum)
ts的enum和java的enum基本一致,使用枚舉類型可以為一組數值賦予友好的名字
let green:Color.Green;
console.log(green);// 輸出 #00FF00
任意值(any)
有時候在編程階段不知道一個變量值的類型,這個值的可能來自于動態內容 這種情況下 我們不希望類型檢查器對這些值進行檢查 而是直接讓它們通過編譯,其實就跟js中直接let一個變量效果是一樣的 該變量可以有任意類型的值
a = "four";
a = true;
a = {}
//以上代碼皆可運行
Null 和 Undefined
ts中 undefined和null有自己各自的類型 分別叫做 undefined 和 null 和void相似 他們本身的類型用處不大
默認情況下 null和undefined是所有類型的子類型 也就是說 你可以把null和undefined 賦值給任何類型的變量
let b:string;
let c:number;
a = null;
b = undefined;
c = undefined;```
## 空值(void)
void類型與any類型正好相反 它表示沒有任何類型 一般用在聲明函數返回值時 如果一個函數沒有返回值 則其返回值類型為void
```function fun():void{
console.log(`Hello Wolrd!`)
}```
聲明一個void型變量沒有多大意義 因為你只能賦予 `undefined` 和 `null`
```let a:void = undefined;```
## Nerver
nerver類型表示那些永遠不存在值的類型 這個類型花里胡哨的 不想看
## Object
`object`表示非原始類型,也就是除`number`,`string`,`boolean`,`symbol`,`null`或`undefined`之外的類型。
使用`object`類型,就可以更好的表示像`Object.create`這樣的API。例如:
```declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error```
# 二、變量聲明
這章沒什么好看的 就是一些 var let const 以及其各種作用域問題 跳過
# 三、接口(interface)
ts的核心原則之一就對值所具有的結構進行檢查 它有時被稱作"鴨式辯型法" 或 "結構性子類型化" 在TS中 接口的作用就是為這些類型命名和你的代碼定義契約
## 鴨式辯型法
第一次聽到這個名字 表情是這樣的  其實理解起來非常簡單,鴨式辯型來自一句名言
> 像鴨子一樣走路并且嘎嘎叫的就叫鴨子 ---------------魯迅

舉個栗子
```interface Duck {
name:string,
age:3
}
function printDuck(d:Duck){
console.log(`打印鴨子:`, d);
}
let obj = {name:"可達鴨",age:18};
printDuck(obj)
上面例子中 變量obj擁有了 Duck的所有屬性 那么obj就是一只鴨子 這就是傳說中的"鴨式辯型"
fine 下面開始正式了解interface
初步了解
好下面來再觀察下接口是如何工作的
//打印人類
function printHuman(human:{name:string,age:number,sex:boolean}){
console.log(`打印人類`,human)
}
let ladyboy = {name: '蔡徐坤', age:38, sex:true,hobby:'唱調 rap 籃球 Music'};
printHuman(ladyboy)
在上面代碼中 類型檢查器會查看 printHuman的調用,printHuman有一個參數,并要求這個對象參數name age sex三個屬性,需要注意的是 我們傳入的對象參數實際上包含很多屬性 但編譯器只會檢查那些必須的屬性書否存在,并且其類型是否匹配,然而 有些時候TS卻不會這么寬松 我們下面會繼續學習
接下在用interface重寫上面的例子
//定義人類
interface Human{
name:string,
age:number,
sex:boolean,
}
//打印人類
function printHuman(human:Human){
console.log(`打印人類`,human)
}
let ladyboy = {name: '蔡徐坤', age:38, sex:true,hobby:'唱調 rap 籃球 Music'};
printHuman(ladyboy)
Human
接口就好比一個名字,用來描述上面例子里的需求,他代表了有 name
age
sex
屬性 且定一個了各自類型的對象 需要注意的是 我們這里不能像在其他語言里一樣說 傳給 printHuman()
的對象實現了這個接口,TS只會去關注值的外形,只要傳入的對象滿足上面提到的條件,那么它就是被允許的,還有要注意的就是 類型檢查器不會去檢查屬性的順序 只要相應的屬性存在并且類型正確即可
可選屬性
接口里的屬性不是全都必需的 有些是只在某些條件下存在或者根本不粗在 可選屬性在應用 ``options bags` 模式很常用 ,即給函數傳入的參數對象中只有部分屬性賦值了
國際慣例//定義人類
interface Human{
name:string,
age:number,
sex:boolean,
hobby?:string
}
//打印人類
function printHuman(human:Human){
console.log(`打印人類`,human)
}
let ladyboy = {name: '蔡徐坤', age:38, sex:true,hobby:'唱調 rap 籃球 Music'};
printHuman(ladyboy)
在上面例子中可以看出 可選屬性和普通接口的定義差不多,只是在可選屬性名字定義后面加一個 問號?
可選屬性的好處之一就是可以對可能存在的屬性進行預定義 好處之二是可以捕獲引用了不存在的屬性時的錯誤比如我們故意將printHuman中的 hobby拼錯誤 就會得到一個錯誤提示
只讀屬性
一些對象屬性只能在對象剛剛創建的時候修改其值 你可以在屬性名前加 readonly
來指定只讀屬性
//定義人類
interface Human{
name:string,
age:number,
readonly sex:string,
hobby?:string,
}
let ladyboy:Human = {name: '蔡徐坤', age:38, sex:'girl',hobby:'唱調 rap 籃球 Music'};
ladyboy.age = 48 //正常執行
ladyboy.sex ='boy'//報錯
上面代碼中 ladyboy的sex在創建后將無法改變 否則會出現如下報錯
只讀數組
TS具有只讀數組 ReadonlyArray<T>
類型 ``Array<T>相似,它只是把所有的可變方法去掉了 如
push` 之類的會改變數組元素的方法 因此可以確保數組創建后不能被修改
let a: number[] = [1,2,3,4];
let b:ReadonlyArray<number> = a;
b[0] = 2; //出錯
b.push(5);//出錯
let c:number[] = b;//出錯 不可以將ReadonlyArray<T> 直接復制給 Array<T>
let d:number[] = <number[]> b;//可以通過類型斷言重寫賦值
console.log(`調試:`,a);
readonly VS const
const 定義只讀變量 readonly定義只讀屬性
額外屬性檢查
name: string,//姓名
age?:number//年齡
}
function createWoman(woman:Woman):Woman{
return woman
}
let cxk:Woman = {name:'蔡徐坤',jjsize:'18cm'};//報錯
let cxk2 = createWoman( {name:'蔡徐坤',jjsize:'18cm'})//報錯
上面代碼會出現如下報錯 因為interface Woman 沒jjsize屬性 TS會認為這段代碼可能存在bug 對象字面量會被特殊對待且經過額外的屬性檢查,當他們賦值給變量或作為參數傳遞的時候 如果一個對象的字面量存在任何 '目標類型' 不包含的屬性時 你會得到一個錯誤
想要繞開這些檢查非常簡單 主要有以下三種方法
方法一:類型斷言
let cxk:Woman = {name:'蔡徐坤',jjsize:'18cm'} as Woman;
方法二:索引簽名
索引簽名是最佳的方式 ,前提是你能夠確定這個對象可能具有某些為特殊用途的額外屬性 如果 Woman
帶有上面定義類型的 name
和 age
屬性 ,并且還帶有任意數量和其他屬性,那么我們可以這樣定義它
interface Woman {
name: string,//姓名
age?:number,//年齡
[propName :string]:any //任意數量任意類型的其他屬性
}
后面會繼續深入學習索引簽名
方法三:變量賦值
interface Woman {
name: string,//姓名
age?:number,//年齡
}
let human = {name:'蔡徐坤',jjsize:'18cm'};
let cxk:Woman = human
這個方法就是將這個對象復制給另變量 huamn
因為 huamn
不會經過額外屬性檢查所以編譯器不會報錯(這里我是看不大懂 憑什么就不經過額外屬性檢查了
這里需要注意的是 huamn
和 Woman
必須要存在共同屬性時才能使用
像下方這樣寫是會報錯的
let human = {jjsize:'18cm'};
let cxk:Woman = human;
函數類型
接口能描述js中各種對象擁有的各樣外形,除了描述帶有屬性的對象外,接口也可以描述函數類型 為了使用接口表示函數類型 我們需要給接口定義一個調用簽名 (就是定義參數列表和返回值類型)
//定義一個方法 Todo 接收 時間 地點 人物 事件 四個參數
interface Todo {
(time:string,where:string,who:string,something:string):boolean
}
let playBasketball:Todo
playBasketball=function(time:string,where:string,who:string,something:string):boolean{
console.log(`Todo:${who}于${time}在${where}${something}`)
return true;
}
playBasketball("清明節","孫笑川墳頭","蔡徐坤","打籃球");
//輸出結果 Todo:蔡徐坤于清明節在孫笑川墳頭打籃球
對于函數的類型檢查來說 函數名不需要與接口里定義的一致,函數的參數會進行逐個檢查 要求對應位置上的參數類型是兼容的 如果你不想指定類型 TS的類型系統會推斷出參數類型
可索引的類型
可索引類型具有一個索引簽名,它描述了對象索引的類型 還有相應索引返回值的類型
interface Woman {
name: string,//姓名
age?:number,//年齡
}
//女團
interface WomansArray {
[index: number]:Woman;
}
//定義女團變量
let womensTeam: WomansArray;
womensTeam = [
{name:'蔡徐坤'},
{name:'鹿晗'},
{name:'張大大'}
]
//單飛的女團成員
let singleWoman:Woman = womensTeam[0];
let singleWoman2:Woman = womensTeam['1']; //字符串與number一致
上面的例子里 我們定義了 WomanArray
接口 它具有索引簽名,這個索引簽名表示了當用 string
去索引WomanArray
時或得到 Woman
類型的返回值
TS支持兩種索引簽名:字符串和數字。可以同時保持兩種類型的索引 但是數字索引的返回值必須是字符串索引返回值的子類型(這句真?繞),這是因為當使用 number
索引時 JS會將它轉換成 string
然后再去索引對象 也就是說用 number
100 去索引等同于用 string
"100" 去索引 因此 兩者需要保持一致
(這一塊還是看不大懂 有空再看)
Class類型
與C#或Java里的基本作用一樣TS也能夠用它來明確的強制一個類去符合某種契約
//動物
interface Animal {
name?:string; //名字 動物不一定都有名字 所以用了可選屬性
eat(food: string):void //描述一個eat(吃) 方法 任何動物都需要進食
}
// Huamn (人類) 實現 Animal 接口
class Huamn implements Animal {
name:string; //名字
sex:string; //性別
age:number; //年齡
//構造函數
constructor(name:string,sex:string,age:number){
this.name = name;
this.sex = sex;
this.age =age;
}
//實現接口中的eat()
eat(food:string){
console.log(`${this.age}歲的${this.name}${this.sex}士正在吃${food}`)
}
}
let cxk:Huamn = new Huamn("蔡徐坤","女",38)
cxk.eat("籃球");//輸出結果 ..... 38歲的蔡徐坤女士正在吃籃球
你可以在接口中描述一個方法,在類里實現它 如同上例中的 setTime
方法一樣,接口描述了類的公共部分 ,而不是公共和私有兩部分 它不會幫你檢查類是否具有私有成員
類靜態部分與實例部分的區別
類具有兩個類型:靜態部分的類型和實例的類型,當用構造器 constructor
簽名去定義一個接口并試圖定義一個類去實現這個接口時會得到一個錯誤 ,這是因為一個類實現了一個接口時,只對其實力部分進行了類型檢查 ``constructor` 屬于類的靜態部分所以不在檢查范圍內,因此我們應該直接操作類的靜態部分
(這一塊太花里胡哨了 遲點再看)
接口繼承
和類一樣 接口也可以相互繼承,這讓我們能夠從一個接口里復制成員到另一個接口里,可以靈活地接口分割到可重用的模塊里
//動物
interface Animal {
name?:string; //名字 動物不一定都有名字 所以用了可選屬性
eat(food: string):void //描述一個eat(吃) 方法 任何動物都需要進食
}
// Huamn (人類) 實現 Animal 接口
interface Huamn extends Animal {
name:string; //名字
sex:string; //性別
age:number; //年齡
}
class Japanese implements Huamn{
name:string; //名字
sex:string; //性別
age:number; //年齡
Country:string = "日本";
constructor(name:string,sex:string,age:number){
this.name = name
this.sex = sex
this.age = age
}
eat(food: string): void {
console.log(`來自${this.Country}${this.age}歲的${this.name}${this.sex}士正在吃${food}`)
}
}
let cxk:Japanese = new Japanese("蔡徐坤","女",38)
cxk.eat("籃球"); //輸出 來自日本38歲的蔡徐坤女士正在吃籃球
一個接口可以繼承多個接口 創建出多個接口合成的接口 //動物
interface Animal {
name?: string; //名字 動物不一定都有名字 所以用了可選屬性
eat(food: string): void //描述一個eat(吃) 方法 任何動物都需要進食
}
// Huamn (人類) 實現 Animal 接口
interface Huamn extends Animal {
name: string; //名字
sex: string; //性別
age: number; //年齡
}
//明星
interface Star {
fans: number//粉絲數
opus: string[] //代表作
}
//假明星 繼承了 Human和Star 兩個接口
interface FakeStar extends Huamn,Star{
buyFakeFans(num:number) //制造假粉絲
sendLawyersLetter(who:string)//發律師函
}
class StupidStar implements FakeStar{
public name:string;
public sex:string;
public fans:number;
public opus:string[]=[];
public age:number;
constructor(name,sex,age,fans){
this.name = name
this.sex = sex
this.age = age
this.fans =fans
}
//添加作品
public addOpus(name:string){
this.opus.push(name)
}
public eat(food: string): void {
console.log(`粉絲量有${this.fans / 10000 }萬,今年${this.age}歲,代表作有${JSON.stringify(this.opus)}的${this.name}${this.sex}士正在吃${food}`)
}
public buyFakeFans(num: number) {
console.log(`粉絲量有${this.fans / 10000 }萬,今年${this.age}歲,代表作有${JSON.stringify(this.opus)}的${this.name}${this.sex}士成功給自己買了${num/10000}萬個假粉絲,`)
this.fans += num
}
public sendLawyersLetter(who: string) {
console.log(`粉絲量有${this.fans / 10000 }萬,今年${this.age}歲,代表作有${JSON.stringify(this.opus)}的${this.name}${this.sex}士給${who}發了一封律師函`)
}
}
let cxk: StupidStar = new StupidStar("蔡徐坤", "女", "38",10000000)
cxk.buyFakeFans(100000); //打印輸出 粉絲量有1000萬,今年38歲,代表作有[]的蔡徐坤女士成功給自己買了10萬個假粉絲
cxk.eat("籃球"); //打印輸出 粉絲量有1010萬,今年38歲,代表作有[]的蔡徐坤女士正在吃籃球
cxk.addOpus("《雞你太美》") //添加作品
cxk.sendLawyersLetter("BiliBili");//粉絲量有1010萬,今年38歲,代表作有["《雞你太美》"]的蔡徐坤女士給BiliBili發了一封律師函
混合類型
這個感覺沒什么用 看不明白 有空再看。下面是原文
先前我們提過,接口能夠描述JavaScript里豐富的類型。 因為JavaScript其動態靈活的特點,有時你會希望一個對象可以同時具有上面提到的多種類型。
一個例子就是,一個對象可以同時做為函數和對象使用,并帶有額外的屬性。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number): string { return '' };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
在使用JavaScript第三方庫的時候,你可能需要像上面那樣去完整地定義類型。
接口繼承類
這是什么騷操作??當接口繼承哼了一個Class類型時 它會繼承Class的成員 但不包括其實現,就好像接口聲明了類中所有存在的成員但并沒有提供具體實現 ,接口同樣會繼承Class的 private` 和
protected` 這意味整合當你創建一個接口繼承了一個擁有私有保護的成員類時 這個接口類型只能被這個類或其子類所實現 當項目中有一個龐大的繼承結構時這很有用 但要注意的是代碼只在子類擁有特定屬性時起作用 除了繼承自基類 子類之間不必相關聯
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
select() { }
}
class Location {
}
在上面的例子里,SelectableControl
包含了Control
的所有成員,包括私有成員state
。 因為state
是私有成員,所以只能夠是Control
的子類們才能實現SelectableControl
接口。 因為只有Control
的子類才能夠擁有一個聲明于Control
的私有成員state
,這對私有成員的兼容性是必需的。
在Control
類內部,是允許通過SelectableControl
的實例來訪問私有成員state
的。 實際上,SelectableControl
就像Control
一樣,并擁有一個select
方法。 Button
和TextBox
類是SelectableControl
的子類(因為它們都繼承自Control
并有select
方法),但Image
和Location
類并不是這樣的
四、類(Class)
基本說明就不說了 跟Java一毛一樣
繼承
TS中我們可以使用常用的面向對象模式,基于類的程序設計中一種最基本的模式是允許使用繼承來拓展現有的類
class Animal {//動物
move(distanceInMeters: number = 0) { //移動方法
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal {//狗
bark() { //叫
console.log('Woof! Woof!');
}
}
const dog = new Dog();//實例化
dog.bark();
dog.move(10);
dog.bark();
super關鍵字
在子類中 super 指向父類的this 實際應用見下例
class Animal{
name:string
constructor(name:string){
this.name = name
}
eat(food:string){
console.log(`${this.name}正在吃${food}`)
}
}
class Human extends Animal{
constructor(name:string) {
super(name);
}
eating(food:string){
super.eat(food)
}
}
let cxk = new Human("蔡徐坤");
cxk.eating("??")
類成員修飾符
類成員共有以下幾種修飾符
public
TS中成員默認都為public 也可以明確的將一個成員標記為 public
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} 移動了 ${distanceInMeters}米.`);
}
}
private
當成員被標記為 private
時 它就不能在類的外部訪問
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 錯誤: 'name' 是私有的.
protected
readonly
學習中....