一、介面初探
- 有時候我們傳入的引數可能會包含很多的屬性,但是編譯器只會檢查那些必須的屬性是否存在,以及型別是否匹配,而我們們要講的介面其實就是用來描述下面這個例子裡的結構,對於介面傳入的資料我們們只關心它的外形,只關心他傳入的物件是夠滿足我們們介面的限定條件,滿足我們們就認定他是正確的
function Animal(config: {name: string, age: number}) {
console.log("我叫:" + config.name + "今年" + config.age + "歲了")
}
console.log(Animal({ name: "yxl", age: 24 }))
// 下面重構
interface Config {
name: string
age: number
}
function Animal(config: Config) {
console.log("我叫:" + config.name + "今年" + config.age + "歲了")
}
/*
* 這裡把介面限制的型別付給了變數myAnimal,即使我們們傳入了介面中沒有限制的屬性,
* 這裡編譯之後也不會出現報錯,是因為介面型別檢查不會對變數賦值進行檢查
* 如果我們們這裡把Animal({name: "yxl", age: 24, color: "黑色的頭髮"})這樣傳參的話,
* 介面型別檢查會報出當前color屬性沒有在Config介面中進行限制,所以是不被允許的
*/
let myAnimal = { name: "yxl", age: 24, color: "黑色的頭髮" }
console.log(Animal(myAnimal))
二、可選屬性
- 介面裡的屬性有時候不是必須的,有的我們們可以用到,有的 用不到,這個時候可選屬性介面就是一個不錯的選擇,可選屬性的好處就是我們們可以對可能存在的屬性進行預先定義,其次是能夠捕獲不存在屬性是的一個錯誤
interface Config {
name: string
age?: number
// [propName: string]: any 字串索引簽名
}
function Animal(config: Config) {
console.log("我叫:" + config.name + "今年" + config.age + "歲了")
}
/*
* 這裡將Animal傳參中的age我們們故意打錯了個字母,這個時候你就會發現當前介面會對這個屬性進行檢查
* 介面會對當前ages報錯,不存在於當前介面規範中,這個時候為了避免這種報錯可以採用型別斷言,告訴這個介面,
* 當前穿的引數就是你的規範,這個時候可以發現錯誤消失了,還有另一種方法是通過索引簽名的方式進行遮蔽錯誤
* [propName: string]: any 這個索引簽名就是為了你能夠預見某個物件可能覺有某些特殊的用途而準備的。
*/
console.log(Animal({ name: "yxl", ages: 24 } as Config))
三、只讀屬性
- 一些物件屬性只能在物件剛剛建立的時候修改其值。 你可以在屬性名前用 readonly 來指定只讀屬性:
interface Point {
readonly x: number
readonly y: number
}
let p:Point = { x: 12, y: 14 }
p.x = 15 // 錯誤
/*
* TypeScript 具有 ReadonlyArray<T> 型別,它與 Array<T> 相似,只是把所有可變方法去掉了,
* 因此可以確保陣列建立後再也不能被修改:
*/
let a: number[] = [1, 2, 3]
let aa: ReadonlyArray<number> = a
aa[0] = 14; // Index signature in type 'readonly number[]' only permits reading.
aa.push(4) // Property 'push' does not exist on type 'readonly number[]'.
aa.length = 10 // Cannot assign to 'length' because it is a read-only property.
a = aa as number[] // 型別斷言為數字組成的陣列
a[0] = 15
四、函式型別
- 介面能夠描述JavaScript中物件擁有的各種各樣的外形。除了描述帶有屬性的普通物件外,介面也可以描述函式型別。接下來我們一起看一下函式型別介面是怎麼使用的
interface SearchFun {
(source: string, subString: string): Boolean
}
let mySearch: SearchFun // @1
/*
* 這裡對宣告瞭一個匿名函式,並且將這個匿名函式付給了變數mySearch,並且同時
* 給這個匿名函式進行了傳參,當然這個傳參遵循了SearchFun介面規範,
* 介面會自動推斷引數的型別,因為我們們已經將mySearch函式付給了SearchFun型別變數
* 注意: 函式類介面不同於屬性類介面,要求屬性的變數相同,這裡變數可以不同,只需要保證變數的型別為string就行
* function(a, b): Boolean
* mySearch = function (a, b): Boolean { 這個方案也可,因為@1中已經將函式付給了介面型別變數
* */
mySearch = function (source: string, subString: string): Boolean {
let result = source.search(subString);
return result > -1
}
console.log(mySearch("http://b28.sy.souyoung.com/ad/cpcAuction", "28")) // true
五、可索引型別
- TypeScript 支援兩種索引簽名:字串和數字。 可以同時使用兩種型別的索引,但是數字索引的返回值必須是字串索引返回值型別的子型別。 這是因為當使用 number 來索引時,JavaScript 會將它轉換成string 然後再去索引物件。 也就是說用 100(一個 number)去索引等同於使用'100'(一個 string )去索引,因此兩者需要保持一致。接下來看看如何使用:
/*
* 第一種情況數字型別索引NumberArr的時候返回值是string 這個時候呼叫a[0] 得到了Bob
* [index: number]: string
* 第二種情況是字串索引NumberArr的時候返回值是string or number,這個時候
* 返回值是string呼叫:a["name"] 得到了Bob 返回值是number呼叫:a["name"] 得到
* 了數字:1
* [index: string]: string
* let a: NumberArr = {
* "name": "Bob"
* }
* * [index: string]: number
* let a: NumberArr = {
* "age": 24
* }
* */
interface NumberArr {
[index: number]: string
}
let a: NumberArr = ["Bob", "Jerry"] // 可
// let a: NumberArr = [55, 66] 不可
console.log(a[0]) // 可
class Animal {
name: string
}
class Dog extends Animal {
breed: string
}
interface NotOkay {
[x: string]: Animal // Numeric index type 'Animal' is not assignable to string index type 'Dog'.
[y: number]: Dog
}
/*
* 上邊的例子很好的詮釋了兩種索引型別可以同時使用,但是這兩者又必須
* 遵循之間的規則,那就是數字索引的返回值必須是字串索引返回值型別的子型別
* 上邊Animal並不是Dog的子型別,剛好弄反了,所以我們們在執行編譯的時候會
* 報錯。下面是正確的使用方式
* interface NotOkay {
* [x: string]: Animal
* [y: number]: Dog
* }
* 下面的例子是對類進行了約束
* */
interface Animal {
// 定義類的name屬性值
name: string;
// 定義類中的方法
eat(name: string, gender: string): void
}
class Dog implements Animal {
name: string;
constructor(name: string){
this.name = name;
};
// 注意介面定義的方法有引數,當你不傳也不會報錯
eat() {
console.log("defind success")
}
}
var dog = new Dog("pika")
dog.eat()
class Cat implements Animal {
name: string;
constructor(name: string){
this.name = name;
};
eat(name: string, gender: string) {
console.log(`${this.name} is ${gender} cat like eat ${name}`)
}
}
var cat = new Cat("herry")
cat.eat("fish", "male")
/*
* 介面描述了類的公共部分,而不是私有跟公共兩部分,他不會幫你檢查類是夠具有某些私有成員。
* 當一個類去實現一個介面的時候,他只會對其例項部分進行檢查。constructor存在於類的靜態部分,所以不再檢查範圍內。
*/
六、介面繼承
- 和類一樣,介面也可以相互繼承。 這讓我們能夠從一個介面裡複製成員到另一個介面裡,可以更靈活地將介面分割到可重用的模組裡。下面是實際應用的例子一起看一下:
interface Animal {
name: string
say(): void
}
interface Person extends Animal {
work(): void
closer: string
}
class Progremmer implements Person {
closer: string;
name: string;
say(): void {
console.log(`${this.name}說我喜歡穿${this.closer}`)
}
work(): void {
console.log(`${this.name}說工作使我快樂!!!`)
}
constructor(name: string, closer: string) {
this.name = name;
this.closer = closer;
}
}
let g:Person = new Progremmer("小明", "花衣服")
g.say();
g.work();
/*
* 上邊中的介面繼承了介面,就意味著person這個介面有了Animal的屬性以及方法
* 這個時候我們們定義的程式設計師類去實現這個Person,實現這個person介面就意味著類必須實現
* 這個介面中的方法跟屬性,上述例子有體現
*/
七、混合型別
- 介面能夠描述 JavaScript 裡豐富的型別。 因為 JavaScript 其動態靈活的特點,有時你會希望一個物件可以同時具有上面提到的多種型別。
interface Counter {
(start: number): string
name: string
say(str: string): void
}
/*
* 宣告一個介面,如果只有(start: number): string一個成員,那麼這個介面就是函式介面,
* 同時還具有其他兩個成員,可以用來描述物件的屬性和方法,這樣就構成了一個混合介面。
*/
function myCounter(): Counter {
function counter(start: number): string {
return ""
}
/*
* function counter實現了介面的描述,成為了一個函式,因為myCounter是Counter型別* 的,所以counter還具有name屬性跟say方法
*/
counter.name = "yxl"
counter.say = function (str: string) {
console.log(`我要學習${str}`)
}
let myCounters: Counter = counter
return myCounters
}
let s = myCounter();
s(10);
s.name= "sss";
s.say("TypeScript");
/*
* 官方文件這裡使用了型別斷言
* function counter(): Counter {
* let myCounters = (function (start: number) { }) as Counter
* myCounters.name = "yxl"
* myCounters.say = function (str: string) {
* console.log(`我要學習${str}`)
* }
* return myCounters
* }
*/