TypeScript 面向對象編程

一缀棍、類

面向對象編程中一個重要的核心就是:宅此,當我們使用面向對象的方式進行編程的時候,通常會首先去分析具體要實現的功能爬范,把特性相似的抽象成一個一個的類父腕,然后通過這些類實例化出來的具體對象來完成具體業(yè)務需求。

【1】類的基礎

在類的基礎中青瀑,包含下面幾個核心的知識點璧亮,也是 TypeScriptEMCAScript2015+在類方面共有的一些特性

  • class 關鍵字
  • 構造函數:constructor
  • 成員屬性定義
  • 成員方法
  • this關鍵字

除了以上的共同特性以外,在 TypeScript 中還有許多 ECMAScript 沒有的斥难,或當前還不支持的一些特性枝嘶,如:抽象

【2】class關鍵字

通過 class 就可以描述和組織一個類的結構,語法:

// 通常類的名稱我們會使用 大坨峰命名 規(guī)則哑诊,也就是 (單詞)首字母大寫
class User {
  // 類的特征都定義在 {} 內部
}

【3】構造函數

通過 class 定義了一個類以后躬络,我們可以通過 new 關鍵字來調用該類從而得到該類型的一個具體對象:也就是實例化。為什么類可以像函數一樣去調用呢搭儒,其實我們執(zhí)行的并不是這個類穷当,而是類中包含的一個特殊函數:構造函數 - constructor

class User {
    constructor() {
    console.log('實例化...')
  }

}
let user1 = new User;

  • 默認情況下提茁,構造函數是一個空函數

  • 構造函數會在類被實例化的時候調用

  • 我們定義的構造函數會覆蓋默認構造函數

  • 如果在實例化(new)一個類的時候無需傳入參數,則可以省略 ()

  • 構造函數 constructor 不允許有return 和返回值類型標注的(因為要返回實例對象)

通常情況下馁菜,我們會把一個類實例化的時候的初始化相關代碼寫在構造函數中茴扁,比如對類成員屬性的初始化賦值

【4】成員屬性與方法定義

class User {
  id: number;
  username: string;

  constructor(id: number, username: string) {
    this.id = id;
    this.username = username;
  }

  postArticle(title: string, content: string): void {
    console.log(`發(fā)表了一篇文章: ${title}`)
  }
}

let user1 = new User(1, 'Demi');
let user2 = new User(2, 'Davi');

【5】this 關鍵字

在類內部,我們可以通過 this 關鍵字來訪問類的成員屬性和方法

class User {
  id: number;
  username: string;

  postArticle(title: string, content: string): void {
    // 在類的內部可以通過 `this` 來訪問成員屬性和方法
    console.log(`${this.username} 發(fā)表了一篇文章: ${title}`)
  }
}

【6】構造函數參數屬性

因為在構造函數中對類成員屬性進行傳參賦值初始化是一個比較常見的場景汪疮,所以 ts 提供了一個簡化操作:給構造函數參數添加修飾符來直接生成成員屬性

  • public 就是類的默認修飾符峭火,表示該成員可以在任何地方進行讀寫操作
class User {

  constructor(
    public id: number,
    public username: string
  ) {
    // 可以省略初始化賦值
  }

  postArticle(title: string, content: string): void {
    console.log(`${this.username} 發(fā)表了一篇文章: ${title}`)
  }
}

let user1 = new User(1, 'Demi');
let user2 = new User(2, 'Davi');

二、繼承

ts 中智嚷,也是通過 extends 關鍵字來實現類的繼承

class VIP extends User {

}

【1】super 關鍵字

在子類中卖丸,我們可以通過 super 來引用父類

  • 如果子類沒有重寫構造函數,則會在默認的 constructor 中調用 super()

  • 如果子類有自己的構造函數盏道,則需要在子類構造函數中顯示的調用父類構造函數 : super(//參數)稍浆,否則會報錯

  • 在子類構造函數中只有在 super(//參數) 之后才能訪問 this

  • 在子類中,可以通過 super 來訪問父類的成員屬性和方法

  • 通過 super 訪問父類的的同時猜嘱,會自動綁定上下文對象為當前子類 this

class VIP extends User {

  constructor(
      id: number,
      username: string,
      public score = 0
    ) {
        super(id, username);
    }

  postAttachment(file: string): void {
    console.log(`${this.username} 上傳了一個附件: ${file}`)
  }
}

let vip1 = new VIP(1, 'Demi');
vip1.postArticle('標題', '內容');
vip1.postAttachment('1.png');

【2】方法的重寫與重載

默認情況下衅枫,子類成員方法集成自父類,但是子類也可以對它們進行重寫和重載

class VIP extends User {

    constructor(
      id: number,
      username: string,
      public score = 0
    ) {
        super(id, username);
    }

    // postArticle 方法重寫朗伶,覆蓋
    postArticle(title: string, content: string): void {
      this.score++;
      console.log(`${this.username} 發(fā)表了一篇文章: ${title}弦撩,積分:${this.score}`);
    }

    postAttachment(file: string): void {
        console.log(`${this.username} 上傳了一個附件: ${file}`)
    }
}

// 具體使用場景
let vip1 = new VIP(1, 'Demi');
vip1.postArticle('標題', '內容');

class VIP extends User {

    constructor(
      id: number,
      username: string,
      public score = 0
    ) {
        super(id, username);
    }

    // 參數個數论皆,參數類型不同:重載
    postArticle(title: string, content: string): void;
    postArticle(title: string, content: string, file: string): void;
    postArticle(title: string, content: string, file?: string) {
        super.postArticle(title, content);

        if (file) {
            this.postAttachment(file);
        }
    }

    postAttachment(file: string): void {
        console.log(`${this.username} 上傳了一個附件: ${file}`)
    }
}

// 具體使用場景
let vip1 = new VIP(1, 'Demi');
vip1.postArticle('標題', '內容');
vip1.postArticle('標題', '內容', '1.png');

三益楼、修飾符

有的時候,我們希望對類成員(屬性点晴、方法)進行一定的訪問控制偏形,來保證數據的安全,通過 類修飾符 可以做到這一點觉鼻,目前 TypeScript 提供了四種修飾符:

  • public:公有俊扭,默認
  • protected:受保護
  • private:私有
  • readonly:只讀

【1】public 修飾符

這個是類成員的默認修飾符,它的訪問級別為:

  • 自身
  • 子類
  • 類外

【2】protected 修飾符

它的訪問級別為:

  • 自身
  • 子類

【3】private 修飾符

它的訪問級別為:

  • 自身

【4】readonly 修飾符

只讀修飾符只能針對成員屬性使用坠陈,且必須在聲明時或構造函數里被初始化萨惑,它的訪問級別為:

  • 自身
  • 子類
  • 類外
class User {

  constructor(
    // 可以訪問,但是一旦確定不能修改
    readonly id: number,

    // 可以訪問仇矾,但是不能外部修改
    protected username: string,

    // 外部包括子類不能訪問庸蔼,也不可修改
    private password: string
  ) {
    // ...
  }
    // ...
}

let user1 = new User(1, 'Demi', '123456');

四、寄存器

有的時候贮匕,我們需要對類成員 屬性 進行更加細膩的控制姐仅,就可以使用 寄存器 來完成這個需求,通過 寄存器,我們可以對類成員屬性的訪問進行攔截并加以控制掏膏,更好的控制成員屬性的設置和訪問邊界拣宰,寄存器分為兩種:

  • getter
  • setter

【1】getter

訪問控制器涌萤,當訪問指定成員屬性時調用

【2】setter- 組件

  • 函數式組件

  • 類式組件

  • props 與 state

  • 組件通信

  • 表單與受控組件

設置控制器,當設置指定成員屬性時調用

class User {

    constructor(
      readonly _id: number,
      readonly _username: string,
      private _password: string
    ) {
    }

    public set password(password: string) {
        if (password.length >= 6) {
            this._password = password;
        }
    }

    public get password() {
        return '******';
    }
    // ...
}

五、靜態(tài)成員

前面我們說到的是成員屬性和方法都是實例對象的燕耿,但是有的時候颠锉,我們需要給類本身添加成員咸产,區(qū)分某成員是靜態(tài)還是實例的:

  • 該成員屬性或方法類型的特征還是實例化對象的特征
  • 如果一個成員方法中沒有使用或依賴 this 陪蜻,那么該方法就是靜態(tài)的
type IAllowFileTypeList = 'png'|'gif'|'jpg'|'jpeg'|'webp';

class VIP extends User {

  // static 必須在 readonly 之前
  static readonly ALLOW_FILE_TYPE_LIST: Array<IAllowFileTypeList> = ['png','gif','jpg','jpeg','webp'];

  constructor(
      id: number,
      username: string,
      private _allowFileTypes: Array<IAllowFileTypeList>
    ) {
        super(id, username);
  }

  info(): void {
    // 類的靜態(tài)成員都是使用 類名.靜態(tài)成員 來訪問
    // VIP 這種類型的用戶允許上傳的所有類型有哪一些
    console.log(VIP.ALLOW_FILE_TYPE_LIST);
    // 當前這個 vip 用戶允許上傳類型有哪一些
    console.log(this._allowFileTypes);
  }
}

let vip1 = new VIP(1, 'Demi', ['jpg','jpeg']);
// 類的靜態(tài)成員都是使用 類名.靜態(tài)成員 來訪問
console.log(VIP.ALLOW_FILE_TYPE_LIST);
this.info();

  • 類的靜態(tài)成員是屬于類的,所以不能通過實例對象(包括 this)來進行訪問腥刹,而是直接通過類名訪問(不管是類內還是類外)
  • 靜態(tài)成員也可以通過訪問修飾符進行修飾
  • 靜態(tài)成員屬性一般約定(非規(guī)定)全大寫

六马胧、抽象類

有的時候,一個基類(父類)的一些方法無法確定具體的行為衔峰,而是由繼承的子類去實現佩脊,看下面的例子:

現在前端比較流行組件化設計,比如 React

class MyComponent extends Component {

  constructor(props) {
    super(props);
    this.state = {}
  }

  render() {
    //...
  }

}

根據上面代碼朽色,我們可以大致設計如下類結構

  • 每個組件都一個 props 屬性邻吞,可以通過構造函數進行初始化组题,由父級定義
  • 每個組件都一個 state 屬性葫男,由父級定義
  • 每個組件都必須有一個 render 的方法
class Component<T1, T2> {

    public state: T2;

    constructor(
        public props: T1
    ) {
        // ...
    }

    render(): string {
        // ...不知道做點啥才好,但是為了避免子類沒有 render 方法而導致組件解析錯誤崔列,父類就用一個默認的 render 去處理可能會出現的錯誤
    }
}

interface IMyComponentProps {
    title: string;
}
interface IMyComponentState {
    val: number;
}
class MyComponent extends Component<IMyComponentProps, IMyComponentState> {

    constructor(props: IMyComponentProps) {
        super(props);

        this.state = {
            val: 1
        }
    }

    render() {
        this.props.title;
        this.state.val;
        return `<div>組件</div>`;
    }

}

上面的代碼雖然從功能上講沒什么太大問題梢褐,但是我們可以看到,父類的 render有點尷尬赵讯,其實我們更應該從代碼層面上去約束子類必須得有 render 方法盈咳,否則編碼就不能通過

【1】abstract 關鍵字

如果一個方法沒有具體的實現方法,則可以通過 abstract 關鍵字進行修飾

abstract class Component<T1, T2> {

    public state: T2;

    constructor(
        public props: T1
    ) {
    }

    public abstract render(): string;
}

使用抽象類有一個好處:

約定了所有繼承子類的所必須實現的方法边翼,使類的設計更加的規(guī)范

使用注意事項:

  • abstract 修飾的方法不能有方法體
  • 如果一個類有抽象方法鱼响,那么該類也必須為抽象的
  • 如果一個類是抽象的,那么就不能使用 new 進行實例化(因為抽象類表名該類有未實現的方法组底,所以不允許實例化)
  • 如果一個子類繼承了一個抽象類丈积,那么該子類就必須實現抽象類中的所有抽象方法,否則該類還得聲明為抽象的

七债鸡、類與接口

在前面我們已經學習了接口的使用江滨,通過接口,我們可以為對象定義一種結構和契約厌均。我們還可以把接口與類進行結合唬滑,通過接口,讓類去強制符合某種契約,從某個方面來說晶密,當一個抽象類中只有抽象的時候擒悬,它就與接口沒有太大區(qū)別了,這個時候惹挟,我們更推薦通過接口的方式來定義契約

  • 抽象類編譯后還是會產生實體代碼茄螃,而接口不會
  • TypeScript 只支持單繼承,即一個子類只能有一個父類连锯,但是一個類可以實現過個接口
  • 接口不能有實現归苍,抽象類可以

【1】implements

在一個類中使用接口并不是使用 extends 關鍵字,而是 implements

  • 與接口類似运怖,如果一個類 implements 了一個接口拼弃,那么就必須實現該接口中定義的契約
  • 多個接口使用 , 分隔
  • implementsextends 可同時存在
interface ILog {
  getInfo(): string;
}

class MyComponent extends Component<IMyComponentProps, IMyComponentState> implements ILog {

    constructor(props: IMyComponentProps) {
        super(props);

        this.state = {
            val: 1
        }
    }

    render() {
        this.props.title;
        this.state.val;
        return `<div>組件</div>`;
    }

    getInfo() {
        return `組件:MyComponent,props:${this.props}摇展,state:${this.state}`;
    }

}

實現多個接口

interface ILog {
    getInfo(): string;
}
interface IStorage {
    save(data: string): void;
}

class MyComponent extends Component<IMyComponentProps, IMyComponentState> implements ILog, IStorage {

    constructor(props: IMyComponentProps) {
        super(props);

        this.state = {
            val: 1
        }
    }

    render() {
        this.props.title;
        this.state.val;
        return `<div>組件</div>`;
    }

    getInfo(): string {
        return `組件:MyComponent吻氧,props:${this.props},state:${this.state}`;
    }

    save(data: string) {
        // ... 存儲
    }

}

接口也可以繼承

interface ILog {
    getInfo(): string;
}
interface IStorage extends ILog {
    save(data: string): void;
}

八咏连、類與對象類型

當我們在 TypeScript 定義一個類的時候盯孙,其實同時定義了兩個不同的類型

  • 類類型(構造函數類型)
  • 對象類型

首先,對象類型好理解祟滴,就是我們的 new 出來的實例類型

那類類型是什么振惰,我們知道 JavaScript 中的類,或者說是 TypeScript 中的類其實本質上還是一個函數垄懂,當然我們也稱為構造函數骑晶,那么這個類或者構造函數本身也是有類型的,那么這個類型就是類的類型

class Person {
    // 屬于類的
  static type = '人';

  // 屬于實例的
  name: string;
  age: number;
  gender: string;

  // 類的構造函數也是屬于類的
  constructor( name: string, age: number, gender: '男'|'女' = '男' ) {
    this.name = name;
    this.age = age;
    this.gender = gender;
  }

  public eat(): void {
    // ...
  }

}

let p1 = new Person('Demi', 24, '女');
p1.eat();
Person.type;

上面例子中草慧,有兩個不同的數據

  • Person 類(構造函數)
  • 通過 Person 實例化出來的對象 p1

對應的也有兩種不同的類型

  • 實例的類型(Person
  • 構造函數的類型(typeof Person

用接口的方式描述如下

interface Person {
    name: string;
    age: number;
    gender: string;
    eat(): void;
}

interface PersonConstructor {
    // new 表示它是一個構造函數
    new (name: string, age: number, gender: '男'|'女'): PersonInstance;
    type: string;
}

在使用的時候要格外注意

function fn1(arg: Person /*如果希望這里傳入的Person 的實例對象*/) {
    arg.eat();
}
fn1( new Person('', 1, '男') );

function fn2(arg: typeof Person /*如果希望傳入的Person構造函數*/) {
    new arg('', 1, '男');
}
fn2(Person);

文章每周持續(xù)更新桶蛔,可以微信搜索「 前端大集錦 」第一時間閱讀,回復【視頻】【書籍】領取200G視頻資料和30本PDF書籍資料

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末漫谷,一起剝皮案震驚了整個濱河市仔雷,隨后出現的幾起案子,更是在濱河造成了極大的恐慌舔示,老刑警劉巖碟婆,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異斩郎,居然都是意外死亡脑融,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門缩宜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肘迎,“玉大人甥温,你說我怎么就攤上這事〖瞬迹” “怎么了姻蚓?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長匣沼。 經常有香客問我狰挡,道長,這世上最難降的妖魔是什么释涛? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任加叁,我火速辦了婚禮,結果婚禮上唇撬,老公的妹妹穿的比我還像新娘它匕。我一直安慰自己,他們只是感情好窖认,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布豫柬。 她就那樣靜靜地躺著,像睡著了一般扑浸。 火紅的嫁衣襯著肌膚如雪烧给。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天喝噪,我揣著相機與錄音础嫡,去河邊找鬼。 笑死仙逻,一個胖子當著我的面吹牛驰吓,可吹牛的內容都是我干的涧尿。 我是一名探鬼主播系奉,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼姑廉!你這毒婦竟也來了缺亮?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤桥言,失蹤者是張志新(化名)和其女友劉穎萌踱,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體号阿,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡并鸵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了扔涧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片园担。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡届谈,死狀恐怖,靈堂內的尸體忽然破棺而出弯汰,到底是詐尸還是另有隱情艰山,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布咏闪,位于F島的核電站曙搬,受9級特大地震影響,放射性物質發(fā)生泄漏鸽嫂。R本人自食惡果不足惜纵装,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望据某。 院中可真熱鬧搂擦,春花似錦、人聲如沸哗脖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽才避。三九已至橱夭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桑逝,已是汗流浹背棘劣。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留楞遏,地道東北人茬暇。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像寡喝,于是被迫代替她去往敵國和親糙俗。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容