面試官:說說你對 TypeScript 中類的理解?應用場景?
本文轉載自微信公眾號「JS每日一題」,作者灰灰。轉載本文請聯系JS每日一題公眾號。
一、是什么
類(Class)是面向對象程序設計(OOP,Object-Oriented Programming)實現信息封裝的基礎
類是一種用戶定義的引用數據類型,也稱類類型
傳統的面向對象語言基本都是基于類的,JavaScript 基于原型的方式讓開發者多了很多理解成本
在 ES6 之后,JavaScript 擁有了 class 關鍵字,雖然本質依然是構造函數,但是使用起來已經方便了許多
但是JavaScript 的class依然有一些特性還沒有加入,比如修飾符和抽象類
TypeScript 的 class 支持面向對象的所有特性,比如 類、接口等
二、使用方式
定義類的關鍵字為 class,后面緊跟類名,類可以包含以下幾個模塊(類的數據成員):
- 「字段」 :字段是類里面聲明的變量。字段表示對象的有關數據。
- 「構造函數」:類實例化時調用,可以為類的對象分配內存。
- 「方法」:方法為對象要執行的操作
如下例子:
- class Car {
- // 字段
- engine:string;
- // 構造函數
- constructor(engine:string) {
- this.engine = engine
- }
- // 方法
- disp():void {
- console.log("發動機為 : "+this.engine)
- }
- }
繼承
類的繼承使用過extends的關鍵字
- class Animal {
- move(distanceInMeters: number = 0) {
- console.log(`Animal moved ${distanceInMeters}m.`);
- }
- }
- class Dog extends Animal {
- bark() {
- console.log('Woof! Woof!');
- }
- }
- const dog = new Dog();
- dog.bark();
- dog.move(10);
- dog.bark();
Dog是一個 派生類,它派生自 Animal 基類,派生類通常被稱作子類,基類通常被稱作 超類
Dog類繼承了Animal類,因此實例dog也能夠使用Animal類move方法
同樣,類繼承后,子類可以對父類的方法重新定義,這個過程稱之為方法的重寫,通過super關鍵字是對父類的直接引用,該關鍵字可以引用父類的屬性和方法,如下:
- class PrinterClass {
- doPrint():void {
- console.log("父類的 doPrint() 方法。")
- }
- }
- class StringPrinter extends PrinterClass {
- doPrint():void {
- super.doPrint() // 調用父類的函數
- console.log("子類的 doPrint()方法。")
- }
- }
修飾符
可以看到,上述的形式跟ES6十分的相似,typescript在此基礎上添加了三種修飾符:
- 公共 public:可以自由的訪問類程序里定義的成員
- 私有 private:只能夠在該類的內部進行訪問
- 受保護 protect:除了在該類的內部可以訪問,還可以在子類中仍然可以訪問
私有修飾符
只能夠在該類的內部進行訪問,實例對象并不能夠訪問
并且繼承該類的子類并不能訪問,如下圖所示:
受保護修飾符
跟私有修飾符很相似,實例對象同樣不能訪問受保護的屬性,如下:
有一點不同的是 protected 成員在子類中仍然可以訪問
除了上述修飾符之外,還有只讀「修飾符」
只讀修飾符
通過readonly關鍵字進行聲明,只讀屬性必須在聲明時或構造函數里被初始化,如下:
除了實例屬性之外,同樣存在靜態屬性
靜態屬性
這些屬性存在于類本身上面而不是類的實例上,通過static進行定義,訪問這些屬性需要通過 類型.靜態屬性 的這種形式訪問,如下所示:
- class Square {
- static width = '100px'
- }
- console.log(Square.width) // 100px
上述的類都能發現一個特點就是,都能夠被實例化,在 typescript中,還存在一種抽象類
抽象類
抽象類做為其它派生類的基類使用,它們一般不會直接被實例化,不同于接口,抽象類可以包含成員的實現細節
abstract關鍵字是用于定義抽象類和在抽象類內部定義抽象方法,如下所示:
- abstract class Animal {
- abstract makeSound(): void;
- move(): void {
- console.log('roaming the earch...');
- }
- }
這種類并不能被實例化,通常需要我們創建子類去繼承,如下:
- class Cat extends Animal {
- makeSound() {
- console.log('miao miao')
- }
- }
- const cat = new Cat()
- cat.makeSound() // miao miao
- cat.move() // roaming the earch...
三、應用場景
除了日常借助類的特性完成日常業務代碼,還可以將類(class)也可以作為接口,尤其在 React 工程中是很常用的,如下:
export default class Carousel extends React.Component
由于組件需要傳入 props 的類型 Props ,同時有需要設置默認 props 即 defaultProps,這時候更加適合使用class作為接口
先聲明一個類,這個類包含組件 props 所需的類型和初始值:
- // props的類型
- export default class Props {
- public children: Array<React.ReactElement<any>> | React.ReactElement<any> | never[] = []
- public speed: number = 500
- public height: number = 160
- public animation: string = 'easeInOutQuad'
- public isAuto: boolean = true
- public autoPlayInterval: number = 4500
- public afterChange: () => {}
- public beforeChange: () => {}
- public selesctedColor: string
- public showDots: boolean = true
- }
當我們需要傳入 props 類型的時候直接將 Props 作為接口傳入,此時 Props 的作用就是接口,而當需要我們設置defaultProps初始值的時候,我們只需要:
- public static defaultProps = new Props()
Props 的實例就是 defaultProps 的初始值,這就是 class作為接口的實際應用,我們用一個 class 起到了接口和設置初始值兩個作用,方便統一管理,減少了代碼量
參考文獻
https://www.tslang.cn/docs/handbook/classes.html
https://www.runoob.com/typescript/ts-class.html