一缀棍、類
面向對象編程中一個重要的核心就是:類
宅此,當我們使用面向對象的方式進行編程的時候,通常會首先去分析具體要實現的功能爬范,把特性相似的抽象成一個一個的類父腕,然后通過這些類實例化出來的具體對象來完成具體業(yè)務需求。
【1】類的基礎
在類的基礎中青瀑,包含下面幾個核心的知識點璧亮,也是 TypeScript
與 EMCAScript2015+
在類方面共有的一些特性
-
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
了一個接口拼弃,那么就必須實現該接口中定義的契約 - 多個接口使用
,
分隔 -
implements
與extends
可同時存在
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書籍資料