Class 類
TypeScript 支持ES2015
中引入的關(guān)鍵字class
與其他JavaScript語言功能一樣, TypeScript也為class
添加了類型注釋和其他語法, 以允許你表達類和其他類型之間的關(guān)系
1. class 成員
這是一個基本的類,只是一個空類
class Point{
}
這個類還不是很有用,所以我們開始添加一些成員
1.1. 字段
1.1.1 字段(屬性)的理解
字段聲明在類上創(chuàng)建公共可寫屬性
class Point{
// 這些字段在在類實例化后會成為實例對象的屬性
x: number
y: number
}
// 實例化
const pt = new Point()
pt.x = 0;
pt.y = 0;
console.log('pt',pt)
/*
{x: 0,y: 0}
*/
類的成員x
, y
為公共的可寫屬性, 也就是類實例上的屬性
與其他位置一樣, 類型注釋是可選的, 但是如果未指定,將默認為any
類型
class Point{
x; // 類型: (property) Point.x: any
y; // 類型: (property) Point.y: any
}
字段也可以有初始器, 這些將在類被實例化時自動運行
class Point{
x = 0;
y = 0;
// (property) Point.x: number
// (property) Point.y: number
}
const pt = new Point()
console.log(`${pt.x}, ${pt.y}`); // 0 ,0
就像var
,const
, let
定義變量一樣, 類屬性的初始器也將會用于推斷其類型
如果賦值類型不匹配將報錯
class Point{
x = 0;
y = 0;
}
const pt = new Point()
pt.x = '0'
// 不能將類型“string”分配給類型“number”
初始化賦值為number類型的值, 實例化后賦予string類型的值將警告
1.1.2 readonly 只讀
字段可以使用readonly
修飾符為前綴, 這樣可以防止對構(gòu)造函數(shù)之外的字段進行賦值
例如:
class Greeter {
readonly name: string = 'world'
constructor(otherName?:string){
if(otherName !== undefined){
this.name = otherName
}
}
err(){
this.name = 'not ok'
// 報錯:無法分配到 "name" ,因為它是只讀屬性枚碗。
}
}
const g = new Greeter()
g.name = 'also not ok'
// 無法分配到 "name" 襟齿,因為它是只讀屬性。
只讀屬性值可以在constructor
構(gòu)造函數(shù)中進行修改賦值
例如:
const h = new Greeter('world')
console.log('h',h)
// {name: 'world'}
類中初始name
的hello
在構(gòu)造函數(shù)constructor
被調(diào)用時修改為world
1.2 構(gòu)造函數(shù)
類的構(gòu)造函數(shù)與普通函數(shù)非常相似, 你可以添加帶有類型注釋, 默認值和重載的參數(shù)
例如:
class Person{
name:string;
age:number;
// 構(gòu)造函數(shù)
// name 參數(shù)添加了類型注釋
// age 參數(shù)使用了默認值, 自動推斷類型為number
constructor(name:string, age = 18){
// (parameter) age: number
this.name = name
this.age = age
}
}
在構(gòu)造函數(shù)中, name
屬性, 添加了類型注釋, age
屬性添加了默認值
構(gòu)造函數(shù)也可以使用重載
class Person{
name:string;
age:number;
className: number; // 班級
// 重載
// 定義了兩個重載, 一個接受兩個參數(shù), 第二個接受一個number類型參數(shù)
constructor(name:string,age:number)
constructor(className:number);
// 重載的實現(xiàn)
constructor(nameOrClassName:string | number, age = 18){
if(typeof nameOrClassName == 'string'){
this.name = nameOrClassName
}else{
this.className = nameOrClassName
}
this.age = age
}
}
類構(gòu)造函數(shù)簽名和函數(shù)簽名之間只有一些區(qū)別:
- 構(gòu)造函數(shù)不能有類型參數(shù)-他們屬于外部類聲明, 稍后了解
- 構(gòu)造函數(shù)不能有返回類型注釋- 類實例類型總是返回的
構(gòu)造函數(shù)中super
關(guān)鍵字
注意,就像在JavaScript中一樣, 如果有一個基類, 你需要使用super()
來繼承基類的屬性或方法時, 在 任何this.成員之前調(diào)用你的構(gòu)造函數(shù)體
例如
// 基類
class Base{
age = 16
}
// 派生類
class Person extends Base{
constructor(){
// 錯誤的寫法:訪問派生類的構(gòu)造函數(shù)中的 "this" 前寞冯,必須調(diào)用 "super"院水。
this.age = 20
super()
}
}
在JavaScript中, 忘記調(diào)用super
是一個很容易犯的錯誤, 但TypeScript會在必要時告訴你
例如:
// 派生類
class Person extends Base{
constructor(){
this.age = 20
// 不使用super 報錯:
// 派生類的構(gòu)造函數(shù)必須包含 "super" 調(diào)用。
}
}
1.3 方法
類上的函數(shù)屬性稱為方法, 方法可以使用所有與函數(shù)和構(gòu)造函數(shù)相同的類型注釋
例如:
class Person{
name:string
age: number
constructor(name:string, age:number){
this.name = name
this.age = age
}
// 方法(類實例化后,原型上的方法)
sayHello(name:string):void{
console.log(`hello ${name}, 我叫${this.name}`)
}
}
const student = new Person('張三',18)
student.sayHello('李四')
// console: hello 李四, 我叫張三
除了標準的類型注釋, TypeScript沒有為方法添加任何新的東西
請注意,在方法體內(nèi), 仍然必須通過this.
的方式調(diào)用主體中非限定名稱的成員, 將始終引用封閉范圍內(nèi)的某些內(nèi)容
例如:
let x:number = 10
class C{
x:string = 'hello'
m(){
x = 'world'
// 不能將類型“string”分配給類型“number”简十。
// 不使用this.成員的方式, 直接使用x 查找的不是類成員,而是不同的變量x
}
}
示例中調(diào)用的x
并不是類主體范圍內(nèi)的x
屬性, 而是外部的變量x, 因此不能通過將string類型的值賦值給number類型的變量
1.4 getters / setters
類也可以有訪問器
例如:
class C{
_length = 0;
// getter
get length(){
return this._length
}
// setter
set length(value){
this._length = value
}
}
請注意檬某,沒有額外邏輯的由字段支持的 get/set 對在 JavaScript 中很少有用。如果您不需要在 get/set 操作期間添加其他邏輯螟蝙,則可以公開公共字段恢恼。
Typescript對訪問器有一些特殊的推理規(guī)則
- 如果
get
存在但不存在set
, 則屬性自動推斷為readonly
屬性 - 如果不指定
setter
參數(shù)的類型, 則從getter
的返回類型推斷 -
Getter
和Setter
必須具有相同成員可見性
從TypeScript4.3
開始, 可以使用不同類型的訪問器來獲取和設(shè)置
例如:
class Thing {
_size = 0;
get size(): number {
return this._size;
}
set size(value: string | number | boolean) {
let num = Number(value);
// Don't allow NaN, Infinity, etc
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
示例中, 在使用訪問器設(shè)置參數(shù)可以為string, number, boolean的聯(lián)合類型
但是因為屬性是number類型, 所有需要在訪問器中通過Number 轉(zhuǎn)換類型
1.5 索引簽名
類可以聲明索引簽名; 這與 其他對象類型的索引簽名相同
處理構(gòu)造函數(shù), 類型中的成員都必須滿足索引簽名
例如:
class MyClass{
// 索引簽名: 屬性的值為Boolean類型或一個方法接受一個string類型參數(shù)并返回boolean類型
[s:string]: boolean | ((s:string) => boolean)
// 屬性
isName = false
// 方法
check(s:string){
return true
}
}
如果不滿足索引簽名的匹配則報錯
class MyClass{
// 索引簽名: 屬性的值為Boolean類型或一個方法接受一個string類型參數(shù)并返回boolean類型
[s:string]: boolean | ((s:string) => boolean)
// 屬性
isName = 10
// 報錯: 類型“number”的屬性“isName”不能賦給“string”索引類型“boolean | ((s: string) => boolean)”。ts(2411)
// 方法
check(s:string){
return this[s]
}
// 報錯:類型“(s: string) => boolean | ((s: string) => boolean)”的屬性“check”不能賦給“string”索引類型“boolean | ((s: string) => boolean)”
}
索引簽名類型還需要捕獲方法的類型, 所以要有效的使用這些類型并不容易,
通常最好將索引數(shù)據(jù)存儲在了另一個地方而不是類實例 本身
2. 類的實現(xiàn)
2.1implements
實現(xiàn)
你可以使用implements
關(guān)鍵字來檢查一個類是否滿足特定接口interface
, 如果一個類未能正確實現(xiàn)這個接口, 則會發(fā)錯錯誤
簡單說:就是類中成員要實現(xiàn)接口定義成員
例如:
// 接口中聲明了一個方法
interface Person{
name: string //接口中的屬性
sayHello():void // 接口中的方法
}
// Student 類通過 implements 實現(xiàn)接口 Person 成員
class Student implements Person{
// 實現(xiàn)接口中的屬性
name :string
// 實現(xiàn)接口中的方法
sayHello() {
console.log('hello')
}
}
// Doctor 未實現(xiàn) 接口中的sayHello 方法 ,因此 報錯
class Doctor implements Person{
say() {
console.log('hello')
}
}
/*
類“Doctor”錯誤實現(xiàn)接口“Person”胰默。
類型 "Doctor" 中缺少屬性 "sayHello"场斑,但類型 "Person" 中需要該屬性。
*/
示例中Student
實現(xiàn)了接口Person
, 但是Doctor
類沒有實現(xiàn), 所有報錯, 告訴接口中需要sayHello
屬性, 但Doctor
沒有實現(xiàn)它
2.2 類實現(xiàn)多個接口
類也可以實現(xiàn)多個接口, 語法如下:
class C implements A, B {}
// C為類, A,B為接口
例如:
// 接口中聲明了一個方法
interface Person{
sayHello():void
}
// 接口
interface Base{
name:string;
age: number;
}
// 類實現(xiàn)兩個接口
class Student implements Person,Base{
// name,age 實現(xiàn) Base接口
name = 'hello';
age = 18
// sayHello 實現(xiàn)Person 接口
sayHello() {
console.log('hello')
}
}
注意事項:
一個很重要的理解:implements
關(guān)鍵字只是檢查類是否可以被視為接口類型, 它根本不會改變類的類型或其他方法,一個常見的錯誤就是假設(shè)implement
關(guān)鍵字會改變類的類型, 但是它不會
例如:
// 接口
interface Checkable{
check(name:string):boolean
}
class NameCheck implements Checkable{
check(s) {
// (parameter) s: any
// 注意此時參數(shù)那么為any類型, 并不會因為implements, 而導(dǎo)致參數(shù)類型發(fā)生改變(變?yōu)榻涌诙x的參數(shù)類型)
return s.toLowercase() === 'ok'
}
}
在這個例子中, 我們可以預(yù)計參數(shù)s
的類型會受到接口中name:string
參數(shù)的影響, 但implements
不會更改檢查類主體或推斷其類型的方式
因此示例中,參數(shù)s
還是按照正常的推斷規(guī)則, 被推斷為any
類型
2.3 接口中存在可選屬性
同樣, 如果 接口中使用了可選屬性,那么在實現(xiàn)接口時不會創(chuàng)建該屬性
// 接口,age為可選屬性
interface Person{
name: string
age?:number
}
// 1.類實現(xiàn), age可選屬性沒有被實現(xiàn)
class Student implements Person{
name: string
}
// 實例化類
const stu = new Student()
// 實例對象上不存在age屬性, 因為類沒有實現(xiàn)age屬性
stu.age = 10
// Doctror實現(xiàn)了接口中的可選屬性
class Doctor implements Person{
name: string
age:number
}
// 2.類實現(xiàn)了可選屬性,實例對象上就可以操作可選屬性
const doc = new Doctor()
doc.age = 20
3. extends 類繼承
與其他具有面向?qū)ο筇匦缘恼Z言一樣牵署,JavaScript 中的類可以從基類繼承漏隐。
類可能extends
繼承基類, 派生類具有其基類的所有屬性和方法, 并且還可以定義其他成員
例如:
// 基類
class Animal{
move(){
console.log('Moving along')
}
}
// 派生類
class Dog extends Animal{
woof(times:number){
for(let i = 0; i< times; i++ ){
console.log('woof!')
}
}
}
const dog = new Dog()
dog.move()
// Moving along
dog.woof(3)
// 3 woof!
示例中Dog
類雖然沒有定義move 方法, 但是其實例對象依然可以使用move方法
原因在于Dog
類是extends
繼承基類Animal
類的派生類, 其會自動繼承基類的成員
3.1 覆蓋方法
派生類也可以覆蓋其基類字段或?qū)傩? 你可以使用super
語法來訪問基類方法,
TypeScript強制派生類始終是其基類的子類型
例如, 下面的這種覆蓋方法是合法的方式
// 基類
class Base{
greet(){
console.log('hello world')
}
}
// 派生類
class Derived extends Base {
greet(name?:string){
if(name === undefined){
// 如果沒有傳遞參數(shù), 則調(diào)用父類的greet方法
super.greet()
}else{
// 如果有參數(shù), 則使用派生類自己的覆蓋
console.log(`Hello, ${name.toUpperCase()}`)
}
}
}
const d = new Derived()
d.greet()
// hello world
d.greet('reader')
// Hello, READER
派生類遵循其基類契約很重要, 請記住, 通過基類類型注釋來引用派生類的實例是很常見的(而且總是合法的)
// 通過基類類型注釋來應(yīng)用派生類的實例
const b:Base = d;
b.greet()
// hello world
b.greet('jack')
// Hello, JACK
示例中,變量b
的類型注釋是基類Base
, 但是賦值給變量b
的確實派生類的實例對象
如果派生類不遵循基類的契約怎么辦?
// 基類
class Base{
greet(){
console.log('hello world')
}
}
// 派生類
class Derived extends Base {
greet(name:string){
console.log(`Hello, ${name.toUpperCase()}`)
}
}
// 錯誤:
/*
類型“Derived”中的屬性“greet”不可分配給基類型“Base”中的同一屬性。
不能將類型“(name: string) => void”分配給類型“() => void”
*/
示例中: 基類Base
中g(shù)reet 方法是沒有參數(shù)的, 但是派生類Derived中greet
方法的參數(shù)是必傳參數(shù),
此時TypeScript就會發(fā)出錯誤, 提示派生類中的屬性greet
不可分配給基類Base
中的同一屬性
如果此時還將派生類的實例賦值給使用基類類型注釋的變量, 也將會發(fā)出錯誤
const b: Base = new Derived();
/*
錯誤:
不能將類型“Derived”分配給類型“Base”奴迅。
屬性“greet”的類型不兼容青责。
不能將類型“(name: string) => void”分配給類型“() => void”
*/
示例中報錯, 因為派生類和基類中的greet
屬性不兼容
3.2 僅類型字段聲明
當(dāng)target >= ES2022
或者useDefinedForClassFields
這是為ture 時, 類字段在父類構(gòu)造函數(shù)完成后初始化
覆蓋父類設(shè)置的任何值, 當(dāng)你只想為繼承的字段重新聲明更為精確的類型時, 這可能會成為問題
為了處理這種情況, 你可以使用declare
向TypeScript表明這個字段聲明不應(yīng)該有運行時影響
例如:
// 接口
interface Animal{
dataOfBirth:any
}
// 接口擴展
interface Dog extends Animal{
breed: any
}
// 基類
class AnimalHouse{
resident: Animal;
constructor(animal:Animal){
this.resident = animal
}
}
// 派生類
class DogHouse extends AnimalHouse{
// 不會對JavaScript 代碼運行有任何影響
// 只是讓屬性的類型更加精準
declare resident:Dog;
constructor(dog: Dog){
super(dog)
}
}
3.3 初始化順序
在某些情況下, JavaScript類的初始化順序可能會令人驚訝,
例如:
// 基類
class Base{
name = 'base'
constructor(){
console.log('My name is '+ this.name)
}
}
// 派生類
class Derived extends Base{
name = 'derived'
}
const d = new Derived()
// 打印: My name is base
示例中答應(yīng)name這是base
, 而不是derived
那這里發(fā)生了什么?
JavaScript定義的類初始化順序是:
- 基類字段被初始化
name = 'base'
- 基類構(gòu)造函數(shù)運行,
Base中constructor 執(zhí)行
- 派生類字段被初始化:
name = 'derived'
- 派生構(gòu)造函數(shù)運行
這意味著基類構(gòu)造函數(shù)name
在其自己的構(gòu)造函數(shù)中看到了自己的值, 因為此時派生類字段初始化尚未運行
4. 類成員可見性
你可以使用TypeScript來控制某些方法或?qū)傩允欠駥︻愅獠康拇a可見
4.1 public 公共
類成員默認可見性是public
, 可以在任何地方訪問成員
例如
// 類
class Greeter{
public greet(){
console.log('hello')
}
}
// 實例
const g = new Greeter()
g.greet()
因為public
可見性修飾符 已經(jīng)是默認的, 所以你不需要在 類的成員上編寫此修飾符, 但有時可能出于樣式/可讀性的原因肯能會選擇添加
4.2 protected 受保護的
protected
成員僅對聲明它們的類以及當(dāng)前類的子類可見
// 類
class Greeter{
// 公共的
public greet(){
console.log('hello, ' + this.getName())
}
// 受保護的
protected getName(){
return 'jack'
}
}
// 派生類(子類)
class SpecialGreeter extends Greeter{
public howdy(){
console.log('howdy, ' + this.getName())
}
}
// 基類的實例
const g = new Greeter()
g.greet()
g.getName()
// 錯誤: 屬性“getName”受保護,只能在類“Greeter”及其子類中訪問
// 派生類(子類)實例
const s = new SpecialGreeter()
s.howdy()
s.getName()
// 錯誤: 屬性“getName”受保護取具,只能在類“Greeter”及其子類中訪問
通過示例了解protected
修飾的屬性和方法,只能在類內(nèi)部使用, 不能再類的實例上調(diào)用
protected 成員曝光
派生類需要遵循其基類的契約, 但可以選擇公開具有更過功能的的基類子類型, 這包括是protected
成員public
化
例如:
// 基類
class Base{
protected num = 10
}
// 派生類
class Dervied extends Base{
// 沒有任何修飾符, 當(dāng)前num就是public
num = 50
}
const d = new Dervied()
console.log(d.num) // 50
請注意, Dervied
類中的num
屬性已經(jīng)可以自由讀寫, 因此這要要注意, protected
修飾的屬性主要在派生類中,如果沒有添加修飾, 當(dāng)前同名屬性將變?yōu)?code>public, 如果這種暴露不是 故意的, 那么我們就需要小心重復(fù)修飾符
<>
4.3 private 私有的
private 就像protected
, 但不允許子類訪問該成員, 只能在當(dāng)前類中訪問
例如:
// 基類
class Base{
private num = 10
}
// 派生類
class Dervied extends Base{
showNum(){
console.log(this.num)
// 屬性“num”為私有屬性脖隶,只能在類“Base”中訪問。
}
}
// 基類實例化
const b = new Base()
console.log(b.num)
// 屬性“num”為私有屬性暇检,只能在類“Base”中訪問
// 派生類實例化
const d = new Dervied()
d.showNum()
示例中,發(fā)現(xiàn)private
修飾的屬性為私有屬性, 只能在當(dāng)前類中訪問, 和protected
很像
protected
修飾符的屬性為受保護的屬性,只能在類中訪問,可以是當(dāng)前類也是派生類
因為private 成員對派生類不可見, 所以派生類不能增加其可見性(否則報錯)
// 基類
class Base{
private num = 10
showNum(){
console.log(this.num)
}
}
// 派生類
class Dervied extends Base{
num = 20
}
/*
類“Dervied”錯誤擴展基類“Base”产阱。
屬性“num”在類型“Base”中是私有屬性,但在類型“Dervied”中不是
*/
跨實例訪問private 成員
TypeScript允許跨實例訪問private
成員
class A{
private x = 10;
sameAs(other:A){
return other.x === this.x
}
}
// 實例一
const a = new A()
// 實例二
const b = new A()
const bol = b.sameAs(a)
console.log('bol',bol)
// bol true
注意事項:
與TypeScript類型系統(tǒng)的其他方面一樣,private
,protected
只在類型檢查期間強制執(zhí)行
這意味著像in
或者簡單的JavaScript運行時構(gòu)造的屬性查找任然可以訪問private
, protected
成員
// 類
class MySafe{
private num = 123
}
// 實例
const s = new MySafe()
console.log(s.num)
// TypeScript報錯: 屬性“num”為私有屬性块仆,只能在類“MySafe”中訪問构蹬。
// 編譯后, JavaScript運行, 依然可以答應(yīng)出 123
private
還允許在類型檢查期間使用括號表示法進行訪問。這使得private
-declared 字段可能更容易訪問單元測試等內(nèi)容悔据,缺點是這些字段是軟私有的并且不嚴格執(zhí)行隱私庄敛。
class MySafe{
private num = 123
}
const s = new MySafe()
// 通過中括號訪問不報錯
console.log(s['num']) // 123
與TypeScript的private
修飾符不同, JavaScript的私有字段(#
) 在編譯后仍然是私有的, 并且不提供前面提到的轉(zhuǎn)移艙口(如括號符號訪問), 這使得他們成為了硬私有的
class Person {
#num = 10;
name = '張三';
constructor(){ }
}
// 實例化
const student = new Person()
console.log(student['#num']) // undefined 獲取不到值
如果您需要保護類中的值免受惡意行為者的侵害,您應(yīng)該使用提供硬運行時隱私的機制蜜暑,例如閉包铐姚、WeakMaps 或私有字段。請注意,這些在運行時添加的隱私檢查可能會影響性能隐绵。
5. 靜態(tài)成員
類可能有static
成員, 這些成員不與類的特定實例相關(guān)聯(lián), 他們可以通過類構(gòu)造函數(shù)對象本身訪問:
例如:
// 類
class MyClass{
static num = 10;
static printNum(){
console.log(MyClass.num)
}
}
// 通過類名訪問static靜態(tài)成員
console.log(MyClass.num)
MyClass.printNum()
靜態(tài)成員也可以使用相同的public
, protected
, 以及private
修飾符
// 類
class MyClass{
// 靜態(tài)屬性num 使用 了私有private 修飾符
// 此時num 只能在MyClass類中訪問
private static num = 10;
static printNum(){
// ok
console.log(MyClass.num)
}
}
console.log(MyClass.num)
// 錯誤: 屬性“num”為私有屬性之众,只能在類“MyClass”中訪問。
MyClass.printNum()
靜態(tài)成員也會被繼承
// 基類
class Base{
static getGreeting(){
return 'hello world'
}
}
// 派生類
class Derived extends Base{
myGreeting = Derived.getGreeting()
}
5.1 特殊靜態(tài)名稱
Function
從原型覆蓋屬性通常是不安全/不可能的, 因為類本省就是可以調(diào)用的函數(shù). 所以不能將諸如類的名稱, name, length, 和類函數(shù)屬性call
定義為static
成員
class Person{
static name = 'hello'
// 靜態(tài)屬性“name”與構(gòu)造函數(shù)“Person”的內(nèi)置屬性函數(shù)“name”沖突
}
6.類中的 static 塊(代碼塊)
靜態(tài)塊允許你編寫具有自己范圍的語句序列,這些語句可以訪問包含類中的私有字段, 這意味著我們可以編寫具有編寫語句的所有功能的初始代碼, 不會泄露變量, 并且可以完全訪問我們類的內(nèi)部結(jié)構(gòu)
例如:
// 類型
class Foo{
static #count = 0
get count(){
return Foo.#count;
}
static {
// 靜態(tài)快
try {
const num = Math.floor(Math.random() * 10 )
Foo.#count += num
}catch(error){
}
}
}
7. 泛型類
類, 很像接口, 可以是泛型的,當(dāng)使用實例化泛型類時, 其類型參數(shù)的推斷方式與函數(shù)調(diào)用中方式相同
例如:
// 泛型類
class Box<Type>{
contents: Type
constructor(value: Type){
this.contents = value
}
}
// 實例化
const b = new Box("hello")
// const b: Box<string>
console.log('b',b)
類可以像接口一樣使用通用約束和默認值
7.1 靜態(tài)成員中的類型參數(shù)
例:
// 泛型類
class Box<Type>{
static defaultValue: Type
// 錯誤: 靜態(tài)成員不能引用類類型參數(shù)依许。
}
示例中的代碼不合法, 原因在于類型總是會被完全擦除的
在運行時,只有一個Box.defaultValue
屬性槽, 這意味著設(shè)置Box<string>.defaultValue
(如果可能的話)也會被改變?yōu)?code>Box<number>.defaultValue, 這樣就很不好,
泛型類的static
成員 永遠不能引用類的類型參數(shù)
8. 參數(shù)屬性
TypeScript提供了特殊的語法,用于將構(gòu)造函數(shù)參數(shù)轉(zhuǎn)換為相同的名稱和值的類屬性, 這些稱謂參數(shù)屬性
是通過在構(gòu)造函數(shù)參數(shù)前面加上修飾符public
,protected
, private
或readonly
.來創(chuàng)建,
沒有參數(shù)屬性前,如果想讓參數(shù)作為屬性, 需要賦值處理
例如:
class Params {
x: number;
y: number;
// 構(gòu)造函數(shù)
constructor(x:number,y:number){
this.x = x;
this.y = y;
}
}
// 實例化
const a = new Params(10,20)
console.log(a) // {}
現(xiàn)在通過在參數(shù)前添加修飾符, 參數(shù)直接可以變成屬性
例如:
// 類
class Params {
constructor(
public readonly x :number ,
protected y:number ,
private z:number
){
// ...
}
}
const a = new Params(1,2,3)
console.log(a)
/*
添加完修飾符,編譯后運行, 瀏覽器打印的a對象上就有x,y,z三個參數(shù)屬性
{x: 1, y: 2, z: 3}
*/
// 1.x 屬性為公共只讀屬性
console.log(a.x)
// (property) Params.x: number
a.x = 20
// 錯誤:無法分配到 "x" 棺禾,因為它是只讀屬性
// 2. y 屬性為受保護屬性, 實例上無法獲取
console.log(a.y)
// 錯誤:屬性“y”受保護,只能在類“Params”及其子類中訪問
// 3. z屬性為私有的,只有在當(dāng)前類中可以訪問
console.log(a.z)
// 錯誤:屬性“z”為私有屬性峭跳,只能在類“Params”中訪問膘婶。
10.類的表達式
類的表達式與類聲明非常相似, 唯一真正的區(qū)別是類的表達式不需要定義名稱, 我們可以通過他們最終綁定到的任何標識符來引用它們
例如:
// 類的表達式
const SomeClass = class<Type> {
content:Type;
constructor(value:Type){
this.content = value
}
}
// 實例化
const m = new SomeClass("hello world")
// const m: SomeClass<string>
11 abstract
抽象類和成員
11.1 抽象類和抽象成員
TypeScript中的類, 方法, 和字段可能是抽象的
抽象方法或抽象字段是尚未提供實現(xiàn)的方法, 有點像重載,只定義解構(gòu)類型,
這些抽象成員必須存在于抽象類中, 不能直接實例化
抽象類的作用是作為實現(xiàn)所有抽象成員的子類的基類, 但一個類沒有任何抽象成員時, 就說他是具體的類
例子:
// 抽象類
abstract class Base{
// 抽象方法
abstract getName():string
printName(){
console.log('Hello '+ this.getName())
}
}
// 實例化抽象類
const b = new Base()
// 報錯: 無法創(chuàng)建抽象類的實例
我們無法實例化Base
, 因為他是抽象類, 相反,我們需要創(chuàng)建一個派生類并實現(xiàn)抽象成員
// 抽象類
abstract class Base{
name: string
constructor(name:string){
this.name = name
}
// 抽象方法
abstract getName():string
printName(){
console.log('Hello '+ this.getName())
}
}
// 派生類: 實現(xiàn)抽象成員
class Student extends Base{
age: number
constructor(name:string,age:number){
super(name)
this.age = age
}
// 抽象成員的實現(xiàn)
getName(): string {
return this.name
}
}
// 實例化派生類(實現(xiàn)類)
const b = new Student('小明',18)
b.printName()
請注意, 如果我們忘記實現(xiàn)基類(抽象類)的抽象成員, 我們會得到一個錯誤
// 派生類: 實現(xiàn)抽象成員
class Student extends Base{
// 錯誤: 非抽象類“Derived”不會實現(xiàn)繼承自“Base”類的抽象成員“getName”。
}
抽象類做為其他派生的基類使用,他們一般不會直接被實例化,不同于接口,抽象類可以包含成員的實現(xiàn)細節(jié)
abstract 關(guān)鍵字是用于定義抽象類和在抽象類內(nèi)部定義抽象方法
11.2. 抽象構(gòu)造簽名
有時你想接受一些類構(gòu)造函數(shù), 它產(chǎn)生一個派生自某個抽象類的類的實例
例如:
// 抽象類
abstract class Base{
// 抽象方法
abstract getName():string
printName(){
console.log('Hello '+ this.getName())
}
}
function greet(ctor: typeof Base){
const instance = new ctor()
// 報錯: 無法創(chuàng)建抽象類的實例蛀醉。
instance.printName()
}
Typescript 正確的告訴您, 您正在嘗試實例化一個抽象類,
相反, 你想編寫一個接受帶有構(gòu)造函數(shù)簽名函數(shù)
function greet(ctor: new () => Base){
const instance = new ctor()
instance.printName()
}
greet(Derived)
greet(Base)
/*
報錯:
類型“typeof Base”的參數(shù)不能賦給類型“new () => Base”的參數(shù)悬襟。
無法將抽象構(gòu)造函數(shù)類型分配給非抽象構(gòu)造函數(shù)類型。
*/
現(xiàn)在 TypeScript 正確地告訴您可以調(diào)用哪些類構(gòu)造函數(shù) -Derived
可以拯刁,因為它是具體的脊岳,但Base
不能。
12. 類之間的關(guān)系
在大多數(shù)情況下, TypeScript中的類在結(jié)構(gòu)上進行比較, 與其他類型相同
例如, 如下兩個類可以相互替代使用, 因為他們是相同的
// 類1
class Point1{
x = 0;
y = 0;
}
// 類2
class Point2{
x = 0;
y = 0;
}
// OK
const p:Point1 = new Point2()
因為兩個類相同, 所有可以將Point2
類的實例化對象賦值給使用Point1
類作為類型注釋的變量
同樣, 即使沒有顯示繼承, 類之間的子類型關(guān)系也是存在
// 隱式的子類
class Person {
name:string;
age:number
}
// 隱式的父類
class Employee{
name: string;
age: number;
salary: number;
}
// ok
const p:Person = new Employee()
父類的實例化對象可以賦值給使用子類作為類型注釋的變量,
但是需要注意, 反過來就不可以,因為子類的實例化可能不滿足父類中的成員
空類沒有成員, 在結(jié)構(gòu)類型系統(tǒng)中, 沒有成員的類型通常是其他任何東西的超類型.
所以如果你寫了一個空類(盡量不要), 那么任何東西都可以代替它
// 空類
class Empty{}
function fn(x:Empty){
console.log(x)
}
// 以前全部ok
fn(window)
fn({})
fn(fn)
13. 類的其他用法
類除了可是實現(xiàn)接口外, 接口也可以反過來擴展類,
簡單說就是可以把類當(dāng)作接口使用
例如:
// 把類當(dāng)成接口使用(相當(dāng)于一個接口)
class Person{
name:string
age: number
}
interface Student extends Person{
sex: string
}
let xiaoming:Student = {name:"小明", age: 18, sex: "男"}
console.log('xiaoming', xiaoming)