# typescript 11
## 11-2 使用函式建構子
跳過
## 11-3 使用類別
```typescript=
interface Person {
id: string
name: string
city: string
}
class Employee {
// 使用 class 的時候物件的屬性需要宣告
id: string
name: string
dept: string
city: string
constructor (id: string, name: string, dept: string, city: string) {
this.id = id
this.name = name
this.dept = dept
this.city = city
}
writeDept () {
console.log(`${this.name} works in ${this.dept}`)
}
}
const salesEmployee = new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris')
const data: (Person | Employee)[] = [
{ id: 'bsmith', name: 'Bob Smith', city: 'London' },
{ id: 'ajones', name: 'Alice Jones', city: 'Paris' },
{ id: 'dpeters', name: 'Dora Peters', city: 'New York' },
salesEmployee
]
data.forEach(item => {
if (item instanceof Employee) {
item.writeDept()
} else {
console.log(`${item.id} ${item.name}, ${item.city}`)
}
})
```
## 11-3-2 屬性的存取控制關鍵字
public (預設)
private
protected
```typescript=
class Employee {
public id: string
public name: string
private readonly dept: string
public city: string
constructor (id: string, name: string, dept: string, city: string) {
this.id = id
this.name = name
this.dept = dept
this.city = city
}
writeDept () {
console.log(`${this.name} works in ${this.dept}`)
}
}
const salesEmployee = new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris')
// src/index.ts(21,38): error TS2341: Property 'dept' is private and only accessible within class 'Employee'.
console.log(`Dept is ${salesEmployee.dept}`)
```
## 11-3-3 定義唯讀屬性
```typescript=
class Employee {
public readonly id: string
public name: string
private readonly dept: string
public city: string
constructor (id: string, name: string, dept: string, city: string) {
this.id = id
this.name = name
this.dept = dept
this.city = city
}
writeDept () {
console.log(`${this.name} works in ${this.dept}`)
}
}
const salesEmployee = new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris')
// src/index.ts(20,15): error TS2540: Cannot assign to 'id' because it is a read-only property.
salesEmployee.id = 'fidel'
```
## 11-3-4 進一步簡化類別建構子
```typescript=
class Employee {
constructor (
public readonly id: string,
public name: string,
private readonly dept: string,
public city: string) {
}
writeDept () {
console.log(`${this.name} works in ${this.dept}`)
}
}
```
## 11-3-5 使用類別繼承
關鍵字
extends
super
```typescript=
class Employee extends Person {
constructor (public readonly id: string,
public name: string,
private readonly dept: string,
public city: string) {
super(id, name, city) // 呼叫父類別的建構式
}
writeDept () {
console.log(`${this.name} works in ${this.dept}`)
}
}
```
## 11-3-6 瞭解子類別的型別推論
如果你放任編譯器自行推論類別子類別的型別,很容易產生預期之外的結果
```typescript=
class Person {
constructor (public id: string,
public name: string,
public city: string) { }
}
class Employee extends Person {
constructor (public readonly id: string,
public name: string,
private readonly dept: string,
public city: string) {
super(id, name, city)
}
writeDept () {
console.log(`${this.name} works in ${this.dept}`)
}
}
class Customer extends Person {
constructor (public readonly id: string,
public name: string,
public city: string,
public creditLimit: number) {
super(id, name, city)
}
}
class Supplier extends Person {
constructor (public readonly id: string,
public name: string,
public city: string,
public companyName: string) {
super(id, name, city)
}
}
// 初始化的時候只有包含 Employee 和 Customer 所以編譯器會自行推論出 data 的型別爲
// declare const data: (Employee | Customer)[];
const data = [
new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris'),
new Customer('ajones', 'Alice Jones', 'London', 500)
]
data.push(new Supplier('dpeters', 'Dora Peters', 'New York', 'Acme'))
data.forEach(item => {
console.log(`Person: ${item.name}, ${item.city}`)
if (item instanceof Employee) {
item.writeDept()
} else if (item instanceof Customer) {
console.log(`Customer ${item.name} has ${item.creditLimit} limit`)
} else if (item instanceof Supplier) {
// src/index.ts(43,11): error TS2345: Argument of type 'Supplier' is not assignable to parameter of type 'Employee | Customer'.
// Property 'creditLimit' is missing in type 'Supplier' but required in type 'Customer'.
// src/index.ts(51,14): error TS2358: The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter.
// src/index.ts(52,34): error TS2339: Property 'name' does not exist on type 'never'.
// src/index.ts(52,57): error TS2339: Property 'companyName' does not exist on type 'never'
console.log(`Supplier ${item.name} works for ${item.companyName}`)
}
})
```
修正:
```typescript=
const data: Person[] = [
new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris'),
new Customer('ajones', 'Alice Jones', 'London', 500)
]
```
## 11-4-1 定義抽象類別 (abstract class)
```typescript=
// 抽象類別
abstract class Person {
constructor (public id: string,
public name: string,
public city: string) { }
getDetails (): string {
return `${this.name}, ${this.getSpecificDetails()}`
}
// 抽象方法
// 可以強迫子類別實作抽象方法
abstract getSpecificDetails (): string
}
```
## 11-4-2 對抽象類別使用型別防衛敘述
```typescript=
abstract class Person {
constructor (public id: string,
public name: string,
public city: string) { }
getDetails (): string {
return `${this.name}, ${this.getSpecificDetails()}`
}
abstract getSpecificDetails (): string
}
class Employee extends Person {
constructor (public readonly id: string,
public name: string,
private readonly dept: string,
public city: string) {
super(id, name, city)
}
getSpecificDetails () {
return `works in ${this.dept}`
}
}
class Customer extends Person {
constructor (public readonly id: string,
public name: string,
public city: string,
public creditLimit: number) {
super(id, name, city)
}
getSpecificDetails () {
return `has ${this.creditLimit} limit`
}
}
class Supplier {
constructor (public readonly id: string,
public name: string,
public city: string,
public companyName: string) { }
}
const data: (Person | Supplier)[] = [
new Employee('fvega', 'Fidel Vega', 'Sales', 'Paris'),
new Customer('ajones', 'Alice Jones', 'London', 500),
new Supplier('dpeters', 'Dora Peters', 'New York', 'Acme')
]
// data.forEach(item => console.log(item.getDetails()));
data.forEach(item => {
if (item instanceof Person) {
console.log(item.getDetails())
} else {
console.log(`${item.name} works for ${item.companyName}`)
}
})
```
## 11-5-1 定義與實作界面
界面與抽象類別的差異:
1. 界面不實作方法
2. 界面不定義建構子,只定義物件的形狀
```typescript=
interface Person {
name: string
getDetails: () => string
}
class Employee implements Person {
constructor (public readonly id: string,
public name: string,
private readonly dept: string,
public city: string) { }
getDetails () {
return `${this.name} works in ${this.dept}`
}
}
```
## 11-5-2 同時實作多個界面
```typescript=
interface Person {
name: string
getDetails: () => string
}
interface DogOwner {
dogName: any
getDogDetails: () => string
}
class Customer implements Person, DogOwner {
constructor (public readonly id: string,
public name: string,
public city: string,
public creditLimit: number,
public dogName) { }
getDetails () {
return `${this.name} has ${this.creditLimit} limit`
}
getDogDetails () {
return `${this.name} has a dog named ${this.dogName}`
}
}
```
實作多個界面的前提是各個界面的同名屬性不能有相斥的型別
譬如
```typescript=
interface Person {
id: string
}
interface DogOwner {
id: integer
}
```
## 11-5-3 界面繼承
```typescript=
interface Person {
name: string
getDetails: () => string
}
// 界面也可以使用 extends 關鍵字
interface DogOwner extends Person {
dogName?: string
getDogDetails?: () => string
}
class Employee implements Person {
constructor (public readonly id: string,
public name: string,
private readonly dept: string,
public city: string) { }
getDetails () {
return `${this.name} works in ${this.dept}`
}
}
```
## 11-5-4 在界面定義選擇性的屬性與方法
```typescript=
// ?
interface DogOwner extends Person {
dogName?: string
getDogDetails?: () => string
}
dogOwners.forEach(item => {
// 執行時要檢查是否有實作選擇性的屬性與方法,避免發生錯誤
if (item.getDogDetails) {
console.log(item.getDogDetails())
}
})
```
## 11-5-5 定義一個實作界面的抽象類別
```typescript=
interface Person {
name: string
getDetails: () => string
dogName?: string
getDogDetails?: () => string
}
abstract class AbstractDogOwner implements Person {
abstract name: string
abstract dogName?: string
abstract getDetails ()
getDogDetails () {
if (this.dogName) {
return `${this.name} has a dog called ${this.dogName}`
}
}
}
class DogOwningCustomer extends AbstractDogOwner {
constructor (public readonly id: string,
public name: string,
public city: string,
public creditLimit: number,
public dogName) {
super()
}
getDetails () {
return `${this.name} has ${this.creditLimit} limit`
}
}
```
## 11-5-6 對介面使用型別防衛敘述
因爲 JS 沒有介面相關的功能,所以 TS 編譯產生出來的程式碼也不會保留介面相關的資訊
所以我們無法使用 instanceof 關鍵字來檢查
```typescript=
data.forEach(item => {
// 不過我們可以檢查介面獨有的成員是否存在
if ('getDetails' in item) {
console.log(`Person: ${item.getDetails()}`)
} else {
console.log(`Product: ${item.name}, ${item.price}`)
}
})
```
## 11-6 動態建立屬性
JS:
```javascript=
const a = {}
a.new_attr = 2 // ok
```
TS:
```typescript=
const a = {}
// src/index.ts(2,3): error TS2339: Property 'new_attr' does not exist on type '{}'.
a.new_attr = 2
```
解決方式:
```typescript=
interface Product {
name: string
price: number
}
class SportsProduct implements Product {
constructor (public name: string,
public category: string,
public price: number) { }
}
class ProductGroup {
[propertypName: string]: Product;
}
const group = new ProductGroup()
group.shoes = new SportsProduct('Shoes', 'Running', 90.50)
group.hat = new SportsProduct('Hat', 'Skiing', 20)
Object.keys(group).forEach(k => {
console.log(`Property Name: ${k}`)
})
```