一.簡介
TypeScript 完全支持 ES2015 中引入的?class
?關鍵字。
與其他 JavaScript 語言功能一樣,TypeScript 添加了類型注釋和其他語法,以允許你表達類和其他類型之間的關系。
1.字段
(1).在申明時同時給出類型
class Person {name: string;age: number;
}
(2).申明時不給出類型
如果不給出類型,那么typescript會自動推斷為any類型的數據
class Person {name; // any類型age; // any類型
}
(3).聲明時給出初值
typescript會自動推斷出來數據的類型是什么,比如下面的就會推斷出來數據的類型為string與number
class Person {name = "尋謬~流光"; // stringage = 30; // number
}
(4).打開了TypeScript 的配置項strictPropertyInitialization
只要打開,就會檢查屬性是否設置了初值,如果沒有就報錯。
如果你打開了這個設置,但是某些情況下,不是在聲明時賦值或在構造方法里面賦值,為了防止這個設置報錯,可以使用非空斷言。
class person {name!: string;age!: number;
}
屬性名后面添加了感嘆號,表示這兩個屬性肯定不會為空,所以 TypeScript 就不報錯了
2.readonly 修飾符
屬性名前面加上 readonly 修飾符,就表示該屬性是只讀的。實例對象不能修改這個屬性。
- 屬性前面有 readonly 修飾符,實例對象修改這個屬性就會報錯。
- 構造方法內部可以設置只讀屬性的初值
- 構造方法可以修改只讀屬性的值
class Person {readonly name = "尋謬~流光";readonly age!: number;// 哎構造函數內部可以自由修改readonly屬性,不管是已經賦值了的,或者是沒有賦值的數據類型constructor(age: number) {this.age = age;this.age = 20;}
}const p = new Person(18);
console.log(p.name); // 尋謬~流光
console.log(p.age); // 20p.age = 25; // 編譯器報錯, 不能對readonly屬性重新賦值
3.構造器
constructor
?方法是?class?的一個特殊方法,用于創建和初始化該類的對象實例。
類構造函數與函數非常相似。你可以添加帶有類型注釋、默認值和重載的參數
class Person {name = "尋謬~流光";age!: number;//通過給參數設置默認值,實現類似重載的效果。constructor(age: number = 18, name: string = "尋謬~流光") {this.age = age;this.name = name;}
}let p1 = new Person();
let p2 = new Person(20, "尋覓~流光");
let p3 = new Person(undefined, "你好");console.log(p1); // Person { name: '尋謬~流光', age: 18 }
console.log(p2); // Person { name: '尋覓~流光', age: 20 }
console.log(p3); // Person { name: '你好', age: 18 }
4.方法
類上的函數屬性稱為方法。
類的方法就是普通函數,類型聲明方式與函數一致。
class Person {name = "尋謬~流光";age!: number;constructor(age: number = 18, name: string = "尋謬~流光") {this.age = age;this.name = name;}// 不添加函數返回值的類型,typescript會自動推導出來這個函數返回的數據類型((method) Person.add_age(num: number): number)add_age(num: number) {this.age += num;return this.age;}// 添加類型注釋low_age(num: number = 1): number {this.age -= num;return this.age;}
}
5.獲取器/設置器
存取器(accessor)是特殊的類方法,包括取值器(getter)和存值器(setter)兩種方法。
它們用于讀寫某個屬性,取值器用來讀取屬性,存值器用來寫入屬性。
class C {_name = "";get name() {return this._name;}set name(value) {this._name = value;}
}
上面示例中,get name()
是取值器,其中get
是關鍵詞,name
是屬性名。外部讀取name
屬性時,實例對象會自動調用這個方法,該方法的返回值就是name
屬性的值。
set name()
是存值器,其中set
是關鍵詞,name
是屬性名。外部寫入name
屬性時,實例對象會自動調用這個方法,并將所賦的值作為函數參數傳入。
(1).如果某個屬性只有get
方法,沒有set
方法,那么該屬性自動成為只讀屬性。
class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}// name為只讀屬性get name(): String {return this._name + "___ABC";}
}
const p = new Person("Tom", 20);
console.log(p.name); // Tom___ABCp.name = "Jerry"; // 報錯,不能修改只讀屬性
(2).set
方法的參數類型,必須兼容get
方法的返回值類型,否則報錯。
class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}get name(): string {return this._name + "___ABC";}// 正確寫法// set name(value: string) {// this._name = value;// }// 報錯set name(value: number) {this._name = value;}
}
(3).get
方法與set
方法的可訪問性必須一致,要么都為公開方法,要么都為私有方法。
正確寫法:
class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}private get name(): string {return this._name + "___ABC";}private set name(value: string) {this._name = value;}
}
錯誤寫法:
class Person {_name: string;_age: number;constructor(name: string, age: number) {(this._name = name), (this._age = age);}private get name(): string {return this._name + "___ABC";}// Get 訪問器必須至少具有與 Setter 相同的可訪問性public set name(value: string) {this._name = value;}
}
6.索引簽名
類可以聲明索引簽名;這些工作與?其他對象類型的索引簽名?相同:
類允許定義屬性索引。
class MyClass {[s: string]: boolean | ((s: string) => boolean);get(s: string) {return this[s] as boolean;}
}
上面示例中,[s:string]
表示所有屬性名類型為字符串的屬性,它們的屬性值要么是布爾值,要么是返回布爾值的函數。
注意,由于類的方法是一種特殊屬性(屬性值為函數的屬性),所以屬性索引的類型定義也涵蓋了方法。如果一個對象同時定義了屬性索引和方法,那么前者必須包含后者的類型。
class MyClass {[s: string]: boolean;f() {// 報錯return true;}
}
上面示例中,屬性索引的類型里面不包括方法,導致后面的方法f()
定義直接報錯。正確的寫法是下面這樣。
class MyClass {[s: string]: boolean | (() => boolean);f() {return true;}
}
屬性存取器等同于方法,也必須包括在屬性索引里面。
class MyClass {[s: string]: boolean;get(s: string) {// 報錯return this[s] as boolean;}
}
上面示例中,屬性索引沒有給出方法的類型,導致get()
方法報錯。
二.類的 interface 接口
1.implements 關鍵字
interface 接口或 type 別名,可以用對象的形式,為 class 指定一組檢查條件。然后,類使用 implements 關鍵字,表示當前類滿足這些外部類型條件的限制。
interface Person {name: string;age: number;
}// 或者這種寫法
// type Person = {
// name: string;
// age: number;
// };class Student implements Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}
}
?你可以使用?implements
?子句來檢查一個類是否滿足特定的?interface
。如果一個類未能正確實現它,則會觸發錯誤:
interface Pingable {ping(): void;
}class Sonar implements Pingable {ping() {console.log("ping!");}
}class Ball implements Pingable {pong() {console.log("pong!");}
}
implements
關鍵字后面,不僅可以是接口,也可以是另一個類。這時,后面的類將被當作接口。
class Person {name: string;age: number;add: (value: number) => number;
}// 實現接口
class Student implements Person {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}add(value: number) {this.age += value;return this.age;}
}
注意,interface 描述的是類的對外接口,也就是實例的公開屬性和公開方法,不能定義私有的屬性和方法。這是因為 TypeScript 設計者認為,私有屬性是類的內部實現,接口作為模板,不應該涉及類的內部代碼寫法。
2.實現多個接口
類可以實現多個接口(其實是接受多重限制),每個接口之間使用逗號分隔。
interface student {//...
}
interface teacher {// ...
}// 繼承多個接口
class class_people implements student, teacher {// ...
}
3.類與接口的合并
TypeScript 不允許兩個同名的類,但是如果一個類和一個接口同名,那么接口會被合并進類。
interface person {name: string;
}
class person {age: number;adress: string;id: number;
}const p: person = {name: "Tom",age: 25,adress: "China",id: 123456,
};console.log(p.name); // Tom
就比如上
定義了person的接口與person的類,接口定義的會被合并到class的類型定義中去
三.類繼承
1.extends
從句
類可能來自基類。派生類具有其基類的所有屬性和方法,還可以定義額外的成員。
class father {name: string;age: number;
}
class son extends father {name: string;age: number;adress: string;
}
let s = new son();
s.name = "Tom";
s.age = 20;
s.adress = "China";
console.log(s);
super(name, age)
:在子類構造函數中,必須先調用父類的構造函數,才能使用?this
。- 如果子類重寫了父類方法,也可以在方法中用?
super.方法名()
?調用父類的方法。
2.super語句
super
?語句用于在子類中調用父類的構造函數或父類的方法。
class Father {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}
}class Son extends Father {adress: string;constructor(name: string, age: number, adress: string) {super(name, age); // 調用父類構造函數this.adress = adress;}
}const s = new Son("Tom", 20, "China");
3.override語句
在 TypeScript 中,override
?關鍵字用于明確表示子類正在重寫父類的成員方法或屬性。這樣可以讓編譯器檢查你是否真的在重寫父類已有的方法,防止拼寫錯誤或父類沒有該方法時出錯。
class Father {name: string;age: number;constructor(name: string, age: number) {this.name = name;this.age = age;}sayHello() {console.log(`Hello, I am ${this.name}`);}
}class Son extends Father {adress: string;constructor(name: string, age: number, adress: string) {super(name, age); // 調用父類構造函數this.adress = adress;}// 重寫父類方法override sayHello() {super.sayHello(); // 可選:調用父類方法console.log(`I live in ${this.adress}`);}
}const s = new Son("Tom", 20, "China");
s.sayHello();
// 輸出:
// Hello, I am Tom
// I live in China
override
?只能用于重寫父類已有的方法或屬性。- 如果父類沒有?sayHello?方法,寫?override sayHello()?會報錯,防止誤寫。
- 推薦在所有重寫父類成員時加上?
override
,代碼更規范、安全。
四.成員可見性
類的內部成員的外部可訪問性,由三個可訪問性修飾符(access modifiers)控制:public
、private
和protected
。
這三個修飾符的位置,都寫在屬性或方法的最前面。
1.public
(1). 基本用法
public
修飾符表示這是公開成員,外部可以自由訪問。
正常情況下,除非為了醒目和代碼可讀性,public
都是省略不寫的。
class Person {public name: string; // 顯式聲明為publicage: number; // 隱式public(默認)public constructor(name: string, age: number) {this.name = name;this.age = age;}public greet() { // 方法默認也是publicreturn `Hello, I'm ${this.name}`;}
}const person = new Person("Alice", 30);
console.log(person.name); // 合法:外部可訪問public成員
person.greet(); // 合法
2.protected
(1). 基本用法
protected
修飾符表示該成員是保護成員,只能在類的內部使用該成員,實例無法使用該成員,但是子類內部可以使用。
class Animal {protected name: string;constructor(name: string) {this.name = name;}protected move(distance: number) {console.log(`${this.name} moved ${distance}m.`);}
}class Dog extends Animal {constructor(name: string) {super(name);}bark() {console.log(`${this.name} barks!`); // 合法:子類可訪問protected成員this.move(5); // 合法:子類可調用protected方法}
}const dog = new Dog("Buddy");
// console.log(dog.name); // 錯誤:實例無法訪問protected成員
// dog.move(10); // 錯誤
dog.bark(); // 合法
(2).子類不僅可以拿到父類的保護成員,還可以定義同名成員。
class Vehicle {protected speed: number = 0;protected accelerate(value: number) {this.speed += value;}
}class Car extends Vehicle {protected speed: number = 10; // 子類可以定義同名protected成員override accelerate(value: number) {this.speed += value * 2; // 子類可以重寫protected方法}
}
(3).在類的外部,實例對象不能讀取保護成員,但是在類的內部可以。
3.private
(1). 基本用法
private
修飾符表示私有成員,只能用在當前類的內部,類的實例和子類都不能使用該成員。
class BankAccount {private balance: number = 0;constructor(initialDeposit: number) {this.balance = initialDeposit;}public deposit(amount: number) {this.balance += amount; // 合法:類內部可訪問private成員}public getBalance() {return this.balance; // 合法}
}const account = new BankAccount(1000);
// console.log(account.balance); // 錯誤:無法訪問private成員
account.deposit(500);
console.log(account.getBalance()); // 合法:通過public方法訪問
(2).子類不能定義父類私有成員的同名成員。
class Base {private code: string = "123";
}class Derived extends Base {// private code: number = 456; // 錯誤:不能重定義父類私有成員
}
(3).如果在類的內部,當前類的實例可以獲取私有成員。
4.實例屬性的簡寫形式
在構造函數參數前加上?
public
、protected
?或?private
,TypeScript 會自動為你聲明并初始化同名實例屬性。這種寫法更簡潔,常用于數據類或 DTO(數據傳輸對象)。
這樣可以讓代碼更簡潔、易讀。
class Config {constructor(public apiKey: string,public timeout: number = 5000,private debug?: boolean) {}
}const config = new Config("123-456");
console.log(config.timeout); // 輸出: 5000 (默認值)
五.靜態成員
類的內部可以使用static
關鍵字,定義靜態成員。
靜態成員的特點:
- 包括靜態屬性和靜態方法,只能通過類名直接訪問,無法通過實例調用。
- 類加載時創建,僅一份
- 子類可以繼承父類的靜態成員,也可以重寫靜態方法
class Person {static value = 1;constructor(public name: string) {}log_value() {console.log(Person.value);}add_value(num: number) {Person.value += num;}
}// 只可以通過類名直接訪問到靜態屬性和方法
// 這里靜態方法同樣如此,就不寫了
console.log(Person.value);let p: Person = new Person("Tom");
let p2: Person = new Person("Jerry");p.log_value(); // 1
p2.log_value(); // 1p.add_value(2);p.log_value(); // 3
p2.log_value(); // 3p2.add_value(3);p.log_value(); // 6
p2.log_value(); // 6
六.抽象類,抽象成員
TypeScript 允許在類的定義前面,加上關鍵字abstract
,表示該類不能被實例化,只能當作其他類的模板。這種類就叫做“抽象類”(abastract class)。
1.抽象類只能當作基類使用,用來在它的基礎上定義子類。
不能實例化抽象類
abstract class Animal {constructor(public name: string, public age: number) {}
}// const Animal_1 = new Animal("Animal", 1); // 錯誤! 不能實例化抽象類class Dog extends Animal {constructor(name: string, age: number, public breed: string) {super(name, age); // 實現構造函數}bark() {console.log(`${this.name} is barking.`);}
}
2.抽象類的子類也可以是抽象類,也就是說,抽象類可以繼承其他抽象類。
abstract class Animal {constructor(public name: string, public age: number) {}
}abstract class Dog extends Animal {constructor(name: string, age: number, public breed: string) {super(name, age);}sound(): void {console.log("W-T-F !");}
}class Labrador extends Dog {constructor(name: string, age: number) {super(name, age, "Labrador");}bark(): void {console.log("Woof, woof!");}
}const myDog = new Labrador("Buddy", 3);
myDog.sound(); // 輸出: W-T-F !
3.抽象成員
如果抽象類的屬性前面加上abstract
,就表明子類必須給出該方法的實現。
abstract class Animal {constructor(public name: string, public age: number) {}abstract makeSound(): void; // 抽象成員函數
}class Dog extends Animal {// 實現抽象成員函數makeSound() {console.log("w t f!");}
}
(1)抽象成員只能存在于抽象類,不能存在于普通類。
(2)抽象成員不能有具體實現的代碼。也就是說,已經實現好的成員前面不能加
abstract
關鍵字。(3)抽象成員前也不能有
private
修飾符,否則無法在子類中實現該成員。(4)一個子類最多只能繼承一個抽象類。
總之,抽象類的作用是,確保各種相關的子類都擁有跟基類相同的接口,可以看作是模板。其中的抽象成員都是必須由子類實現的成員,非抽象成員則表示基類已經實現的、由所有子類共享的成員。
七.this 問題
在類中,this
?始終指向當前實例對象,但這個指向會受到調用方式的影響。
- 當通過?
實例.方法()
?調用時,this
?正常指向實例; - 當方法被單獨提取(如賦值給變量、作為回調)時,
this
?會丟失指向(變成?undefined
?或全局對象)。
這是因為類的方法默認定義在原型上,調用時需要依賴調用者來確定?this
?的指向(即 “誰調用,this
?指向誰”)。
1.常見的this場景問題
(1).方法作為回調函數時,this
?丟失
原因:callback
?只是一個函數引用,調用時沒有 “調用者”,this
?無法指向?user
?實例。
class User {name: string = "Alice";sayHi() {console.log(`Hi, I'm ${this.name}`); // 這里的 this 會在回調中丟失!}
}const user = new User();
// 直接調用:this 指向 user 實例(正常)
user.sayHi(); // 輸出:Hi, I'm Alice// 將方法提取為回調函數
const callback = user.sayHi;
// 單獨調用時,this 指向 undefined(嚴格模式下)
callback(); // 報錯:Cannot read property 'name' of undefined
(2).事件監聽中的?this
?丟失
原因:DOM 事件回調中,this
?默認指向觸發事件的 DOM 元素,覆蓋了類實例的?this
。
class Button {label: string = "Click me";onClick() {console.log(`Clicked ${this.label}`); // 事件觸發時,this 指向觸發事件的 DOM 元素,而非實例}
}const btn = new Button();
// 給按鈕綁定點擊事件(假設頁面有個 id 為 "btn" 的按鈕)
document.getElementById("btn")?.addEventListener("click", btn.onClick);// 當點擊按鈕時:
// 輸出:Clicked undefined(因為 this 指向按鈕 DOM 元素,而非 Button 實例)
(3).箭頭函數與普通方法的?this
?差異
原因:箭頭函數沒有自己的?this
,它的?this
?是定義時捕獲的上下文(此處即類實例)。
class Test {value: number = 10;// 普通方法:this 依賴調用者normalMethod() {console.log(this.value);}// 箭頭函數方法:this 固定指向實例arrowMethod = () => {console.log(this.value);};
}const test = new Test();
const normal = test.normalMethod;
const arrow = test.arrowMethod;normal(); // 報錯:this 丟失
arrow(); // 正常輸出:10(this 始終指向 test 實例)
2.解決?this
?指向問題的 3 種方案
(1).在構造函數中綁定?this
(最常用)
通過?this.方法 = this.方法.bind(this)
?強制綁定?this
?到實例:
class User {name: string = "Alice";constructor() {// 綁定 this 到當前實例this.sayHi = this.sayHi.bind(this);}sayHi() {console.log(`Hi, I'm ${this.name}`);}
}const user = new User();
const callback = user.sayHi;
callback(); // 正常輸出:Hi, I'm Alice(this 已綁定)
(2).使用箭頭函數定義方法(簡潔)
直接將方法定義為箭頭函數,利用箭頭函數的?this
?特性固定指向:
class Button {label: string = "Click me";// 箭頭函數方法:this 永遠指向實例onClick = () => {console.log(`Clicked ${this.label}`);};
}const btn = new Button();
document.getElementById("btn")?.addEventListener("click", btn.onClick);
// 點擊時輸出:Clicked Click me(this 正確指向 Button 實例)
(3).調用時通過?call
/apply
?指定?this
在調用方法時,用?call
?或?apply
?手動指定?this
:
class User {name: string = "Bob";sayHi() {console.log(`Hi, I'm ${this.name}`);}
}const user = new User();
const callback = user.sayHi;// 調用時用 call 綁定 this
callback.call(user); // 輸出:Hi, I'm Bob
八.泛型類
類也可以寫成泛型,使用類型參數。關于泛型的詳細介紹
class person<T> {name: T;age: number;constructor(name: T, age: number) {this.name = name;this.age = age;}
}let p1: person<string> = new person("Tom", 25);
console.log(p1.name); // output: Tom
注意,靜態成員不能使用泛型的類型參數。
下一篇:TypeScript---泛型