在 JavaScript 中 ES6 以前咱們使用函數(構造器函數)和基於原型來建立一個自定義的類,但這種方式總會讓人產生困惑,特別是習慣了 Java、PHP 等面向對象編程的同窗來講更加難以理解。html
面向對象編程的基本單位是對象,但對象又是由類實例化的,因此咱們第一步須要先知道怎麼去聲明一個類。編程
類的聲明使用 class 關鍵詞,類名與變量、函數等命名規則相似,這裏要首寫字母大寫,類名後跟上一對花括號能夠理解爲的類的主體,類的主體三部分組成:成員屬性、構造函數、成員方法。安全
class 類名 {
成員屬性:
構造函數:
成員方法:
}
複製代碼
在類中能夠直接聲明變量,也稱爲成員屬性,另外在類中聲明成員屬性咱們還可使用關鍵詞 private、public、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 關鍵詞修飾的類成員屬性、成員方法是屬於類的與類實例對象無關,且在多個對象之間是共享的。
下例定義了靜態屬性 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 關鍵詞對 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 {
...
}
複製代碼
如下是一個接口的示例:
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 面向對象的封裝、繼承、多態特性時不少概念都是相通的,對於理解給予了很大幫助。