本文目錄:
- 1.創建對象實例
- 2.繼承
- 3.靜態方法和屬性
- 4.訪問修飾符
- 5.readonly
- 6.抽象類
1.創建對象實例
在js中,生成實例對象的傳統方法是通過構造函數
我們首先通過傳統的構造函數和原型對象的方法來看一下對象實例的創建
function Greeter(message) {
this.msg = message;
}
Greeter.prototype.greeter = function() {
return 'hello: ' + this.msg;
};
let m1 = new Greeter('傳統方式創建對象實例');
console.log(m1.msg);
console.log(m1.greeter());
接下來我們再通過類class的方式 生成一個對象實例
我們在ES6的時候,實例屬性都是定義在constructor()方法裏面, 在ES7裏 我們可以直接將這個屬性定義在類的最頂層,其它都不變,去掉this,msg和flag就是我們定義在最頂層的實例屬性
constructor(構造函數)方法是類的默認方法
一個類必須有constructor方法,如果沒有顯示定義,一個空的constructor方法會被默認添加
class Greeter {
msg: string;
flag: boolean = false;
// 關於構造函數:
constructor(message: string) {
this.msg = message;
}
greeter() {
console.log('這個是在constructor構造函數外部定義實例屬性:', this.flag);
return 'hello: ' + this.msg;
}
}
let g2 = new Greeter('通過類創建的對象實例');
console.log(g2.msg);
console.log(g2.greeter());
接下來我們來分析一些,ES6新增的class語法糖,和構造函數的一些關係
類class的類型 本質上是一個函數; 類本身就指向自己的構造函數
console.log(typeof Greeter);
console.log(Greeter === Greeter.prototype.constructor);
console.log(g2.greeter === Greeter.prototype.greeter);
通過上面在這個代碼我們也可以發現,new類的時候就相當於new構造函數
調用類上面的方法就是調用原型上的方法
在類的實例上面調用方法,其實就是調用原型上的方法
2.繼承
- 使用繼承來擴展現有的類,是面向對象的三大特性之一(封裝,繼承,多態)
- 基類,父類,超類是指被繼承的類,派生類,子類是指繼承於基類的類
- ts中類繼承類似於傳統面向對象編程語言中的繼承體系 ,使用extends關鍵字繼承,類中this表示此當前對象本身,super表父類對象。子類構造函數中第一行代碼調用父類構造函數完成初始化,然後再進行子類的進一步初始化。子類中可以訪問父類(public、protected)的成員屬性、方法
- 派生類包含了constructor; ts 規定只要派生類裏面自定義了一個constructor函數就必須在使用this前,調用一下super方法
- ES5 的繼承,實質是先創造子類的實例對象this,然後再將父類的方法添加到this上面(Parent.apply(this));ES6 的繼承機制完全不同,實質是先將父類實例對象的屬性和方法,加到this上面(所以必須先調用super方法),然後再用子類的構造函數修改this
- 因爲子類自己的this對象,必須先通過父類的構造函數完成塑造,得到與父類同樣的實例屬性和方法,然後再對其進行加工,加上子類自己的實例屬性和方法。如果不調用super方法,子類就得不到this對象
子類方法名和父類相同表示重寫父類方法
業務需求:我們現在有兩個類,一個動物類,一個狗類, 狗也是動物,所以會繼承動物類的一些屬性和方法
class Animal {
name: string;
constructor(param: string) {
this.name = param;
}
move(distance: number = 0) {
console.log(`${this.name} 移動了 ${distance}m.`);
}
}
class Dog extends Animal {
bark() {
console.log('狗叫!');
}
}
const dog = new Dog('阿黃');
console.log(dog.name);
dog.bark();
dog.move(10);
dog.bark();
上面這個例子中 動物類是基類,也可以叫父類; 狗是子類也可以叫派生類, 繼承自動物類,可以使用父類的任何方法和屬性
我們將上面的代碼稍微做一下修改
class Dog extends Animal {
dogName2: string;
constructor(name: string) {
// 派生類包含了一個構造函數,就必須首先調用super()方法,會調用基類的構造函數,然後構造子類自己的this
super(name);
this.dogName2 = name;
}
// 父類也有一個move方法,我們在子類例自定義move方法,就會重寫從Animal繼承來的move方法,從而使move方法根據不同的類而實現不同的功能
move(distanceInMeters: number = 5) {
console.log('重寫了基類的move方法');
super.move(distanceInMeters);
}
bark() {
console.log('狗叫!');
}
}
let animal1: Animal = new Animal('赤兔');
let dog1: Dog = new Dog('阿黃');
animal1.move();
dog1.move(10);
這個dog1即使被聲明爲 Animal類型,也不會調用父類的move方法,因爲它的值就是Dog實例
3.靜態方法和屬性
- ES6中提供了 靜態方法, ES7中提供了靜態屬性; TS兩者都有
- 我們可以認爲類具有 實例部分與 靜態部分這兩個部分。定義靜態屬性和方法,只需要在對應的屬性和方法前面加上static即可
class Animal {
static PI = 3.14159;
static isAnimal(param) {
return param instanceof Animal;
}
}
let cat = new Animal();
console.log(Animal.PI);
console.log(Animal.isAnimal(cat));
cat.isAnimal(cat);
cat.PI;
通過對象cat上來調用的屬性和方法 叫做對象實例的屬性和方法
通過類名Animal來調用的 叫靜態屬性和方法
4.訪問修飾符
- ts類中修飾符分爲3種; public : 公有(所有)默認;
protected:保護 (父類+子類);private: 私有(本類)
class Animal {
public name: string;
//修飾符還可以使用在構造函數參數中,等同於類中定義該屬性,使代碼更簡潔
//下面的age屬性就相當於定義在頂部的 一個實例屬性,藉助修飾符也可以定義
public constructor(theName: string, public age: number = 24) {
this.name = theName;
}
public move() {
console.log(123);
}
}
let a1 = new Animal('Lucy');
console.log(a1.name, a1.age);
上面的例子中,name 被設置爲了 public,所以直接訪問實例的 name 屬性是允許的
在ts中,所有的類型默認都是public
- private: 當成員被標記成 private時,它就不能在聲明它的類的外部訪問
class Animal {
// 這個name屬性就只能在這個類裏面訪問,類外部訪問就會報錯
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Dog extends Animal {
constructor(name) {
// 派生類的構造函數必須包含super函數的調用
// 因爲父類的構造函數需要一個參數,所以這裏我們需要將name參數傳遞進去
super(name);
// console.log(this.name); //屬性“name”爲私有屬性,只能在類“Animal”中訪問。所以在派生類裏面訪問也是不允許的
}
}
let a1 = new Animal('Lucy');
console.log(a1.name);
- protected: 屬性和方法 如果是用 protected 修飾,則允許在派生類中訪問, private是不允許的
class Animal {
// 這個name屬性就只能在這個類裏面訪問,類外部訪問就會報錯
protected name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Dog extends Animal {
constructor(name) {
super(name);
// 這個基類的name屬性是 protected受保護的,所以可以在派生類裏面訪問
console.log(this.name);
}
}
let a1 = new Animal('Lucy');
- 構造函數被private修飾, 該類不允許被繼承或者實例化;只允許被繼承
class Animal {
public name;
private constructor(name) {
this.name = name;
}
// protected constructor(name) {
// this.name = name;
// }
}
class Cat extends Animal {
constructor(name) {
super(name);
}
}
let a = new Animal('Jack');
5.readonly
- 只讀屬性關鍵字,只允許出現在屬性聲明或索引簽名中
- 可以使用 readonly關鍵字將屬性設置爲只讀的。 只讀屬性必須在聲明時或構造函數裏被初始化
class Animal {
readonly name: string;
// 聲明是初始化
readonly myName: string = '只讀屬性';
// 注意如果 readonly 和其他訪問修飾符同時存在的話,需要寫在其後面
constructor(name: string, public readonly firstName: string) {
// 構造函數裏面初始化
this.name = name;
}
}
let cat2 = new Animal('阿黃', '小白');
console.log(cat2.name, cat2.myName, cat2.firstName);
cat2.name = '張三'; // 編譯報錯,說不能給一個只讀屬性分配一個新值
6.抽象類
- 抽象類做爲其它派生類的基類使用。 它們一般不會直接被實例化。 不同於接口,抽象類可以包含成員的實現細節
- abstract關鍵字是用於定義抽象類和在抽象類內部定義抽象方法抽象成員
abstract class Animal {
name: string = '基類默認值';
abstract myName: string;
// 僅僅定義方法的簽名,不包含方法體
abstract makeSound(): void;
move(): void {
console.log('動物行走');
}
}
下面這行代碼就會報錯, 無法創建抽象類的實例
抽象類不能被實例化, 只能作爲基類使用,也就是隻能給其他類繼承
let aa2 = new Animal()
抽象類中的抽象方法不包含具體實現並且必須在派生類中實現。 抽象方法的語法與接口方法相似。 兩者都是定義方法簽名但不包含方法體。 然而,抽象方法必須包含 abstract關鍵字並且可以包含訪問修飾符
class Dog extends Animal {
myName: string = '抽象成員';
// 編譯報錯:非抽象類“Dog”不會實現繼承自“Animal”類的抽象成員“makeSound”
// 也就是說我們要將基類的抽象方法在派生類這裏再實現一次
makeSound() {
console.log(`基類的抽象方法必須在派生類中實現--${this.name}--${this.myName}`);
}
}
let aa3 = new Dog();
console.log(aa3.makeSound());