本文爲系列文章《TypeScript 簡明教程》中的一篇。git
TypeScript 中,咱們使用接口來描述對象或類的具體結構。接口的概念在 TypeScript 中是相當重要的。它就像是你與程序簽定的一個契約,定義一個接口就意味着你答應程序:將來的某個值(或者類)必定會符合契約中所規定的模樣,若是不符合,TS 就會直接在編譯時報錯。es6
感興趣的同窗能夠了解一下 鴨子類型。github
舉個例子:編程
interface Phone {
model: string
price: number
}
let newPhone: Phone = {
model: 'iPhone XS',
price: 8599,
}
複製代碼
上面的例子中,咱們定義了一個接口 Phone
,它約定:任何類型爲 Phone
的值,有且只能有兩個屬性:string
類型的 model
屬性以及 number
類型的 price
屬性。以後,咱們聲明瞭一個變量 newPhone
,它的類型爲 Phone
,並且遵守契約,將 model
賦值爲字符串,price
賦值爲數值。數組
接口通常首字母大寫。在某些編程語言會建議使用
I
做爲前綴。關因而否要使用I
前綴,tslint 有一條 專門的規則,請根據團隊編碼風格自行選擇。編程語言
多一些屬性和少一些屬性都是不容許的。函數
let phoneA: Phone = {
model: 'iPhone XS',
} // Error: Property 'price' is missing in type '{ model: string; }' but required in type 'Phone'
let phoneB: Phone = {
model: 'iPhone XS',
price: 8599,
producer: 'Apple',
} // Error: Property 'producer' doesn't exist on type `Phone`.
複製代碼
接口做爲類型註解,只在編譯時起做用,不會出如今最終輸出的 JS 代碼中。ui
對於某個可能存在的屬性,咱們能夠在該屬性後加上 ?標記
表示這個屬性是可選的。this
interface Phone {
model: string
price: number
producer?: string
}
let newPhone: Phone = {
model: 'iPhone XS',
price: 8599, // OK
}
let phoneB: Phone = {
model: 'iPhone XS',
price: 8599,
producer: 'Apple', // OK
}
複製代碼
某些狀況下,咱們可能只知道接口中部分屬性及它們的類型。或者,咱們但願可以在初始化後動態添加對象的屬性。這時,咱們可使用下面這種寫法。編碼
interface Phone {
model: string
price: number
producer?: string
[propName: string]: any
}
let phoneB: Phone = {
model: 'iPhone XS',
price: 8599,
}
phoneB.storage = '256GB' // OK
複製代碼
上面,咱們定義任意屬性的簽名爲 string
類型,值爲 any
類型。注意:任意屬性的值類型必須包含全部已知屬性的值類型。 上述例子中,any
包括 string
和 number
類型。
接口中,咱們可使用 readonly
標記某個屬性是隻讀的。當咱們試圖修改它時,TS 會提示報錯。
interface Phone {
readonly model: string
price: number
}
let phoneA: Phone = {
model: 'iPhone XS',
price: 8599,
}
phoneA.model = 'iPhone Air' // Error: Cannot assign to 'model' because it is a read-only property.
複製代碼
呼,終於說到函數了。JavaScript 中有兩種定義函數的方法。
// 命名函數
function add(x, y) {
return x + y
}
// 匿名函數
const add = function(x, y) { return x + y }
複製代碼
對於這兩種方法,添加類型註釋的方式大同小異。
// 命名函數
function add(x: number, y: number): number {
return x + y
}
// 匿名函數
const add = function(x: number, y: number): number {
return x + y
}
複製代碼
上面咱們定義了 add
函數,它接受兩個 number
類型的參數,並規定其返回值爲 number
類型。
調用函數時,傳入參數的類型和數量必須與定義時保持一致。
add(1, 2) // OK
add('1', 0) // Error
add(1, 2, 3) // Error
複製代碼
使用 ?標記
能夠標識某個參數是可選的。可選參數必須放在必要參數後面。
function increment(x: number, step?: number): number {
return x + (step || 1)
}
increment(10) // => 11
複製代碼
ES6 容許咱們爲參數添加默認值。做爲 JS 的超集,TS 天然也是支持參數默認值的。
function increment(x: number, step: number = 1): number {
return x + step
}
increment(10) // => 11
複製代碼
由於具備參數默認值的參數必然是可選參數,因此無需再使用 ?
標記該參數時可選的。
這裏,step: number = 1
能夠簡寫爲 step = 1
,TS 會根據類型推斷自動推斷出 step
應爲 number
類型。
與可選參數不一樣的是,具備默認值的參數沒必要放在必要參數後面。下面的寫法也是容許的,只是在調用時,必須明確地傳入 undefined
來獲取默認值。
function increment(step = 1, x: number): number {
return x + step
}
increment(undefined, 10) // => 11
複製代碼
ES6 容許咱們使用剩餘參數將一個不定數量的參數表示爲一個數組。TypeScript 中咱們能夠這樣寫。
function sum(...args: number[]): number {
return args.reduce((prev, cur) => prev + cur)
}
sum(1, 2, 3) // => 6
複製代碼
注意與 arguments
對象進行 區分。
對於接口中的方法,咱們可使用以下方式去定義:
interface Animal {
say(text: string): void
}
// 或者
interface Animal {
say: (text: string) => void
}
複製代碼
這兩種註解方法的效果是一致的。
函數重載容許你針對不一樣的參數進行不一樣的處理,進而返回不一樣的數據。
由於 JavaScript 在語言層面並不支持重載,咱們必須在函數體內自行判斷參數進行鍼對性處理,從而模擬出函數重載。
function margin(all: number);
function margin(vertical: number, horizontal: number);
function margin(top: number, right: number, bottom: number, left: number);
function margin(a: number, b?: number, c?: number, d?: number) {
if (b === undefined && c === undefined && d === undefined) {
b = c = d = a
} else if (c === undefined && d === undefined) {
c = a
d = b
}
return {
top: a,
right: b,
bottom: c,
left: d,
}
}
console.log(margin(10))
// => { top: 10, right: 10, bottom: 10, left: 10 }
console.log(margin(10, 20))
// => { top: 10, right: 20, bottom: 10, left: 20 }
console.log(margin(10, 20, 20, 20))
// => { top: 10, right: 20, bottom: 20, left: 20 }
console.log(margin(10, 20, 20))
// Error
複製代碼
上述例子中,前面三個聲明瞭三種函數定義,編譯器會根據這個順序來處理函數調用,最後一個爲最終的函數實現。須要注意的是,最後的函數實現參數類型必須包含以前全部的參數類型定義。所以,在定義重載的時候,必定要把最精確的定義放在最前面。
之前,JavaScript 中並無類的概念,咱們使用原型來模擬類的繼承,直到 ES6 的出現,引入了 class
關鍵字。若是你對 ES6 的 class
還不是很瞭解,建議閱讀 ECMAScript 6 入門 - Class。
TypeScript 除了實現了全部 ES6 中的類的功能之外,還添加了一些新的用法。
TypeScript 中可使用三種修飾符:public
、private
、protected
。
public
修飾符表示屬性或方法是公有的,在類內部、子類內部、類的實例中都能被訪問。默認狀況下,全部屬性和方法都是 public
的。
class Animal {
public name: string
constructor(name) {
this.name = name
}
}
let cat = new Animal('Tom')
console.log(cat.name); // => Tom
複製代碼
private
修飾符表示屬性或方法是私有的,只能在類內部訪問。
class Animal {
private name: string
constructor(name) {
this.name = name
}
greet() {
return `Hello, my name is ${ this.name }.`
}
}
let cat = new Animal('Tom')
console.log(cat.name); // Error: 屬性「name」爲私有屬性,只能在類「Animal」中訪問。
console.log(cat.greet()) // => Hello, my name is Tom.
複製代碼
protected
修飾符表示屬性或方法是受保護的,與 private
近似,不過被 protected
修飾的屬性或方法也能被其子類訪問。
class Animal {
protected name: string
constructor(name) {
this.name = name
}
}
class Cat extends Animal {
constructor(name) {
super(name)
}
greet() {
return `Hello, I'm ${ this.name } the cat.`
}
}
let cat = new Cat('Tom')
console.log(cat.name); // Error: 屬性「name」受保護,只能在類「Animal」及其子類中訪問。
console.log(cat.greet()) // => Hello, I'm Tom the cat.
複製代碼
注意,TypeScript 只作編譯時檢查,當你試圖在類外部訪問被 private
或者 protected
修飾的屬性或方法時,TS 會報錯,可是它並不能阻止你訪問這些屬性或方法。
目前有一個提案,建議在語言層面使用
#
前綴標記某個屬性或方法爲私有的,感興趣的能夠看 這裏。
抽象類是某個類具體實現的抽象表述,做爲其餘類的基類使用。
它具備兩個特色:
TypeScript 中使用 abstract
關鍵字表示抽象類以及其內部的抽象方法。
繼續使用上面的 Animal
類的例子:
abstract class Animal {
public abstract makeSound(): void
public move() {
console.log('Roaming...')
}
}
class Cat extends Animal {
makeSound() {
console.log('Meow~')
}
}
let tom = new Cat()
tom.makeSound() // => 'Meow~'
tom.move() // => 'Roaming...'
複製代碼
上述例子中,咱們建立了一個抽象類 Animal
,它定義了一個抽象方法 makeSound
。而後,咱們定義了一個 Cat
類,繼承自 Animal
。由於 Animal
定義了 makeSound
抽象類,因此咱們必須在 Cat
類裏面實現它。否則的話,TS 會報錯。
// Error: 非抽象類「Cat」沒有實現繼承自「Animal」類的抽象成員「makeSound」。
class Cat extends Animal {
meow() {
console.log('Meow~')
}
}
複製代碼
類能夠實現(implement)接口。經過接口,你能夠強制地指明類遵照某個契約。你能夠在接口中聲明一個方法,而後要求類去具體實現它。
interface ClockInterface {
currentTime: Date
setTime(d: Date)
}
class Clock implements ClockInterface {
currentTime: Date
setTime(d: Date) {
this.currentTime = d
}
}
複製代碼
implement
)多個接口,但只能擴展(extends
)自一個抽象類。instanceof
判斷。接口則只在編譯時起做用。public
)部分,不會檢查私有成員,而抽象類沒有這樣的限制。本篇主要介紹了 TypeScript 中的幾個重要概念:接口、函數和類,知道了如何用接口去描述對象的結構,如何去描述函數的類型以及 TypeScript 中類的用法。