TypeScript 面向對象程序設計(OOP)

在 JavaScript 中 ES6 以前咱們使用函數(構造器函數)和基於原型來建立一個自定義的類,但這種方式總會讓人產生困惑,特別是習慣了 Java、PHP 等面向對象編程的同窗來講更加難以理解。html

抽象一個類

面向對象編程的基本單位是對象,但對象又是由類實例化的,因此咱們第一步須要先知道怎麼去聲明一個類。編程

類的聲明

類的聲明使用 class 關鍵詞,類名與變量、函數等命名規則相似,這裏要首寫字母大寫,類名後跟上一對花括號能夠理解爲的類的主體,類的主體三部分組成:成員屬性、構造函數、成員方法。安全

class 類名 {
  成員屬性:
  
  構造函數:	

  成員方法:
}
複製代碼

成員屬性

在類中能夠直接聲明變量,也稱爲成員屬性,另外在類中聲明成員屬性咱們還可使用關鍵詞 private、public、protected 來修飾:函數

  • public: 聲明的屬性具備公有權限,在類的外部是能夠被訪問的,public 這個關鍵詞默認是能夠不用寫的。
  • private: 聲明的屬性具備私有權限,僅在當前類的內部能夠被訪問。
  • protected: 聲明的屬性具備保護權限,在類的派生類內部能夠被訪問。
class Person {
  age: number; // age 等價於 public age
  private sex: string;
  protected phone: number;
}
複製代碼

構造函數

構造函數用於類的初始化,能夠聲明哪些字段是必傳的,哪些字段是非必傳的。學習

構造函數參數中 public name: string 至關於以下形式:測試

class Person {
  public name: string
  constructor(public name: string) {
    this.name = name;
  }
}
複製代碼

在構造函數內部,能夠爲以前聲明的成員屬性作賦值。ui

class Person {
  ...
  constructor(public name: string, age: number, sex: string, phone?: number) {
    this.age = age;
    this.sex = sex;
    this.phone = phone;
  }
}
複製代碼

成員方法

類中直接聲明函數稱爲成員方法,注意這裏的函數是不須要加 function 關鍵詞的,成員方法要和對象有關聯,例如 eat 方法(每一個都須要吃飯的),另外方法也可使用 public、private、protected 等關鍵詞聲明。this

class Person {
  ...
  public eat() {
    const info = this.info();
    console.log(`${info} eat...`);
  }

  private info() {
    return `${this.name} ${this.age} ${this.sex} ${this.phone}`;
  }
}
複製代碼

什麼是對象?只要能用屬性、方法描述的事物咱們均可以聲明爲一個類,而後對這個類實例化出對象使用。spa

類的實例化對象

上面咱們抽象了一個類 Person,可是在程序中咱們不是直接使用的類,而是經過抽象出來的類來實例化一個或多個對象爲咱們所使用。code

實例化一個對象主要使用 new 關鍵詞,後面跟上須要實例的類。

const zhangsan = new Person('張三', 18, '男');
const lisi = new Person('李四', 20, '男', 18800000000);

zhangsan.eat(); // 張三 18 男 undefined eat...
lisi.eat(); // 李四 20 男 18800000000 eat...
複製代碼

static 靜態屬性

static 能夠用來將類成員屬性、成員方法標識爲靜態的。

static 關鍵詞修飾的類成員屬性、成員方法是屬於類的與類實例對象無關,且在多個對象之間是共享的。

下例定義了靜態屬性 language 爲 chinese,最後實例化了兩個對象,其中 language 可使用類名來調用,且在兩個對象間是共享的。

class Person {
  static language: string = 'chinese';

  constructor(public name: string) {}

  info() {
    console.log(`我叫: ${this.name}, 我說: ${Person.language}`);
  }
}

const zhangsan = new Person('張三');
zhangsan.info(); // 我叫: 張三, 我說: chinese

const lisi = new Person('李四');
lisi.info(); // 我叫: 李四, 我說: chinese
複製代碼

面向對象編程 — 封裝性

對象的成員屬性或方法若是沒有被封裝,實例化後在外部就可經過引用獲取,對於用戶 phone 這種數據,是不能隨意被別人獲取的。

封裝性作爲面向對象編程重要特性之一,它是把類內部成員屬性、成員方法統一保護起來,只保留有限的接口與外部進行聯繫,儘量屏蔽對象的內部細節,防止外部隨意修改內部數據,保證數據的安全性。

Private 關鍵詞修飾

用 private 關鍵詞修飾類的成員屬性和成員方法來實現對成員的封裝,封裝後的成員對象僅能在類的內部被訪問。

下面咱們使用 private 關鍵詞對 Person 類部分紅員進行封裝,可以被外部訪問的只有 info 方法。

class Person {
  private name: string;
  private phone: string;

  constructor(name: string, phone: string) {
    this.name = name;
    this.phone = phone;
  }

  public info() {
    console.log(`我是 ${this.name} 手機號 ${this.formatPhone()}`)
  }

  private formatPhone() {
    return this.phone.replace(/(\d{3})\d{4}(\d{3})/, '$1****$2');
  }
}

const zhangsan = new Person('張三', '18800000000');
zhangsan.info();
複製代碼

私有成員訪問

使用 private 修飾事後的成員屬性或方法在外部將會沒法訪問,若是須要訪問,咱們能夠設置公有方法返回私有屬性,這裏你也能夠作一些條件限制。

class Person {
  private name: string;
  private phone: string;

  constructor(name: string, phone: string) {
    this.name = name;
    this.phone = phone;
  }

  public getName() {
    return this.name;
  }
  ...
}

const zhangsan = new Person('張三', '18800000000');
console.log(zhangsan.getName()); // 張三
複製代碼

面向對象編程 — 繼承性

已存在的用來派生新類的類成爲基類,又可稱爲超類。新派生的類稱爲派生類或子類。

在 C++ 中一個派生類能夠繼承多個基類,有單繼承、多繼承。在 TypeScript、Java、PHP 中都是隻可繼承自一個基類,只有單繼承。

下圖展現一個關於 Person 基類被繼承的示意圖:

子類繼承應用

建立基類 Person 一個成員屬性 name,一個成員方法 eat。

class Person {
  protected name: string;

  constructor(name: string) {
    this.name = name;
  }

  eat() {
    console.log('eat...');
  }
}
複製代碼

建立派生類 Student,經過關鍵詞 extends 繼承於基類 Person,實現一個自定義的 study 方法。

class Student extends Person {
  study() {
    this.eat();
    console.log(`${this.name} 開始學習...`);
  }
}
複製代碼

建立派生類 Work,經過關鍵詞 extends 繼承於基類 Person,實現一個自定義的 work 方法。

class Work extends Person {
  work() {
    super.eat();
    console.log(`${this.name} 開始工做...`);
  }
}
複製代碼

上面建立的兩個子類 Student、Work 都有本身單獨的方法,學生要學習,工人要工做,可是在開始以前都要先吃飽飯吧。

下面咱們測試下這個實例。

const s1 = new Student('張三');
s1.study(); 
// eat... 
// 張三 開始學習...

const w1 = new Work('李四');
w1.work();
// eat...
// 李四 開始工做...
複製代碼

子類重載父類的方法

咱們不能定義重名的函數,也沒法在同一個類中定義重名的方法,可是在派生類中咱們能夠重寫在基類中同名的方法。

class Student extends Person {
  constructor(name: string) { super(name); }

  eat() {
    console.log('今天做業有點多,再加一塊肉!');
    super.eat();
  }

  study() {
    this.eat();
    console.log(`${this.name} 開始學習...`);
  }
}

const s1 = new Student('張三');
s1.study();

// 今天做業有點多,再加一塊肉!
// eat...
// 張三 開始學習...
複製代碼

注意:若是派生類中寫了 constructor() 方法,必須在 this 以前調用 super 方法,它會調用基類的構造函數。

接口繼承

接口多繼承實現

上面講了在 TS 中類之間只能實現單繼承,可是在接口裏是能夠實現單繼承和多繼承的。

interface Person1 {
  nickname: string;
}
interface Person2 {
    age: number;
}
interface Person3 extends Person1, Person2 {
    sex: string;
}

function study(obj: Person3) {
    console.log(obj.nickname, obj.age, obj.sex);
}

study({ nickname: 'may', age: 20, sex: 'man' });
複製代碼

接口繼承類

接口能夠經過 extends 關鍵詞繼承一個類,若是類成員包含實現,則不會被繼承其實現。

class Person1 {
    nickname: string;
    
    test(): string {
        return 'Hello';
    }
}
interface Person2 extends Person1 {
    age: number;
}

class Study implements Person2 {
    nickname: string;
    age: number;
    constructor(nickname: string, age: number) {
        this.nickname = nickname;
        this.age=age;
    }

    test(): string {
        console.log(this.nickname, this.age)
        return 'Hi';
    }
}

const lisi = new Study('李四', 20);
console.log(lisi.test());
複製代碼

面向對象編程 — 多態性

多態性是面向對象編程的三大特性之一,可讓具備繼承關係的不一樣類對象,使用相同的函數名完成不一樣的功能,通俗的講:一個子類能夠修改、重寫父類中定義的相同名稱的方法,父類可使用抽象類或接口來定義相應的規範。

抽象類

抽象類是一種特殊的類,使用 abstract 關鍵詞修飾,通常不會直接被實例化

抽象類中的成員屬性或方法若是沒有用 abstract 關鍵詞修飾,能夠包含實現細節。若是使用 abstract 關鍵詞修飾,則只能定義,實現必需要在派生類中去作。

abstract class Person {
  abstract name: string;
  eat(): void {
    console.log('eat...')
  }

  abstract walk(): void;
}

class Student extends Person {
  name: string;
  walk(): void {
    console.log('walk...');
  }
}

const zhangsan = new Student();
zhangsan.eat(); // eat...
zhangsan.walk(); // walk...
複製代碼

接口

接口是一種特殊的抽象類,與之抽象類不一樣的是,接口沒有具體的實現,只有定義,經過 interface 關鍵詞聲明。

在繼承的時候說過,TypeScript 中只能單繼承,可是在接口這裏,是能夠實現多個接口的。

class 類名 implements Interface1, Interface2 {
  ...
}
複製代碼

如下是一個接口的示例:

  • 定義一個 Person 接口
  • 定義一個 Study 接口,裏面定一個 diploma(文憑)方法
interface Person {
  name: string;
  phone?: string;
}
interface Student {
  diploma(): void;
} 

class HighSchool implements Student, Person {
  name: string;

  diploma(): void {
    console.log('高中生 ...');
  }
}
class University implements Student, Person {
  name: string;

  diploma(): void {
    console.log('大學生 ...')
  }
}
複製代碼

多態性的應用

一個經典的例子,電腦的 USB 接口,咱們能夠插上鼠標、鍵盤、U 盤等設備,來爲其擴展不一樣的功能,每一個設備的功能是不一樣的,可是 USB 接口的規範遵照的是統一的,這也就是咱們所講的多態性,經過聲明抽象類或接口定義規範,子類重寫和父類名稱相同的方法,實現本身的功能

// 定義USB 接口規範
interface USB {
  run(): void;
}

// 實現一個 USB 規範的鍵盤設備
class UKey implements USB {
  run(): void {
    console.log('USB 規範的鍵盤設備');
  }
}

// 實現一個 USB U 盤設備
class UDisk implements USB {
  run(): void {
    console.log('USB 規範的 U 盤設備');
  }
}

// 計算機類
class Computer {
  useUSB(usb: USB) {
    usb.run();
  }
}

const computer = new Computer();
computer.useUSB(new UKey()); // USB 規範的鍵盤設備
computer.useUSB(new UDisk()); // USB 規範的 U 盤設備
複製代碼

總結

大學期間自學了 PHP 開發,當時印象比較深入的一本書是 「細說 PHP」,做者:「高洛峯」 以前暑假去北京實習還見過做者,很 Nice,書中的面向對象這節印象仍是比較深入的,講的很好,在學習 TypeScript 面向對象的封裝、繼承、多態特性時不少概念都是相通的,對於理解給予了很大幫助。

Reference

相關文章
相關標籤/搜索