1. 程式人生 > >《前端之路》- TypeScript (四) class 中各類屬性、方法,抽象類、多型

《前端之路》- TypeScript (四) class 中各類屬性、方法,抽象類、多型

目錄

  • 一、TypeScript 中的類
  • 二、TypeScript 中類的繼承
  • 三、TypeScript 中公共,私有與受保護的修飾符
  • 四、TypeScript 中 靜態方法
  • 五、TypeScript 中 繼承與多型
  • 六、總結

在這一章中介紹的 class 類,希望同學們可以在上一章節中 複習下建構函式、原型、原型鏈等基礎知識

一、TypeScript 中的類

1、先來舉個例子:

class Persons {
  name: any;
  age: number | undefined;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  getName(): void {
    console.log(`${this.name}今年已經${this.age}歲了`);
  }
}

let p11 = new Persons("za", 123);
console.log(p11.getName()); // za今年已經123歲了
  • 轉換成 ES5 的程式碼後:
var Persons = /** @class */ (function() {
  function Persons(name, age) {
    this.name = name;
    this.age = age;
  }
  Persons.prototype.getName = function() {
    console.log(
      this.name + "\u4ECA\u5E74\u5DF2\u7ECF" + this.age + "\u5C81\u4E86"
    );
  };
  return Persons;
})();
var p11 = new Persons("za", 123);
console.log(p11.getName()); //  za今年已經123歲了

2、這裡和我們使用 Es6 中的 class 有一些差別

// javascript 中 class 的定義
class An {
  constructor(name) {
    this.name = name;
  }
  getName() {
    console.log(this.name);
  }
}
var a = new An("zz");
a.getName(); // zz

3、差異在於,我們需要去定義 constructor 建構函式中傳入的資料引數的型別

二、TypeScript 中類的繼承

class Animal {
  name: string | undefined;
  food: string;
  constructor(name: string, food: string) {
    this.name = name;
    this.food = food;
  }
  eat() {
    console.log(`${this.name}吃${this.food}`);
  }
}

class Cat extends Animal {
  constructor(name: string, food: string) {
    super(name, food);
  }
  jump() {
    console.log(`${this.name}正在跳`);
  }
}

let xiaohhua = new Cat("xiaohua", "貓糧");
console.log(xiaohhua.eat()); // xiaohua吃貓糧
console.log(xiaohhua.jump()); // xiaohua正在跳

這裡和 ES6 中的 class 繼承內容基本上沒什麼出入

三、TypeScript 中公共,私有與受保護的修飾符

這裡的修飾符是對類中對 屬性和方法的型別的定義

3-1、屬性的 public

不定義的類心的話,預設就是 public 型別

class Animals {
  public name: string | undefined;
  constructor(name: string) {
    this.name = name;
  }
  eat() {
    console.log(`${this.name}哇`);
  }
}

轉換成 es5 程式碼

"use strict";
var Animals = /** @class */ (function() {
  function Animals(name) {
    this.name = name;
  }
  Animals.prototype.eat = function() {
    console.log(this.name + "\u54C7");
  };
  return Animals;
})();
// 和沒定義之前一樣

3-2、屬性的 private

當成員被標記成 private 時,它就不能在宣告它的類的外部訪問

class Animal2 {
  private name: string | undefined;
  constructor(name: string) {
    this.name = name;
  }
  eat() {
    console.log(`${this.name}哇`);
  }
}

var a = new Animal2("private");
a.name = "123"; // 報錯,name 屬性只能在 Animal2 內部使用
new Animal2("private").name = "432"; // 報錯: 屬性“name”為私有屬性,只能在類“Animal2”中訪問。

3-3、屬性的 protected

當成員被標記成 protected 時,它就不能在宣告它的類的外部訪問,但是該類的子類可以訪問

class Person2 {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}

class exPerson extends Person2 {
  public age: number;
  constructor(age: number, name: string) {
    super(name);
    this.age = age;
    this.name = name;
  }
  public getInfo() {
    console.log(`${this.name}哈哈哈哈${this.age}`);
  }
}

let ps = new exPerson(123, "za"); // 派生類可以繼承 protected 屬性,但是

ps.name = "zz"; // 報錯 外部無法直接訪問
console.log(ps); // { name: 'za', age: 123 }

建構函式也能夠被 設定成 protected 屬性

class Person22 {
  protected name: string;
  protected constructor(name: string) {
    this.name = name;
  }
}

class exPerson2 extends Person2 {
  public age: number;
  constructor(age: number, name: string) {
    super(name);
    this.age = age;
    this.name = name;
  }
  public getInfo() {
    console.log(`${this.name}哈哈哈哈${this.age}`);
  }
}

let exp = new exPerson2(21, "exp-name");
let per22 = new Person22("zs"); // 報錯 類“Person22”的建構函式是受保護的,僅可在類宣告中訪問

3-4、readonly 修飾符

使用 readonly 關鍵字將屬性設定為只讀的。 只讀屬性必須在宣告時或建構函式裡被初始化

class octPers {
  readonly name: string;
  readonly age: number = 8;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

let ns = new octPers("zz", 123);
console.log("---1", ns);
ns.age = 456; // 報錯 Cannot assign to 'age' because it is a read-only property.
console.log("---2", ns); // 這裡會執行什麼內容呢?

四、TypeScript 中 靜態方法

這裡所謂的靜態方法,其實就是將方法直接定義在了 建構函式物件上,只有建構函式本身才能去使用它,任何其他都無法使用(包括它的 派生類)

class staticPerson {
  public name: string;
  public age: number = 8;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  static getName1() {
    console.log("---static-getName---", this);
  }
  protected getName(): void {
    console.log("---protected-getName---", this);
  }
}

let ress = new staticPerson("zzs", 123);
console.log("---instancing getName", staticPerson.getName1()); // 屬性“getName”受保護,只能在類“staticPerson”及其子類中訪問。

五、TypeScript 中 繼承與多型

這裡面其實更多的是 JS 的繼承與多型,我們以 ES5 和 ES6 分別對繼承和多型進行對比

5-1 ES5 中是如何實現 繼承的?

這裡我們想想繼承,到底是繼承什麼?如何繼承?為什麼要繼承?

5-1-1 通過類式繼承

類的方式,其核心在於將 子類的 prototype 指向了 父類的例項,這樣的話,子類的例項的 __proto__ 指向子類的 prototype, 然而 子類的 prototype 被賦予了 父類的例項。我們製作一個簡單的圖,來說明一下這裡如何實現的繼承。

var SuperClass = function(name) {
    var id = 1;
    this.name = name;
    this.work = function() {
        console.log(this.name + 'in SuperClass');
    };
};
SuperClass.prototype.getSuperName = function() {
    return this.name;
};

var SubClass = function() {
    this.getSubName = function() {
        console.log('this is subname');
    };
};

SubClass.prototype = new SuperClass('superClass');
var sub = new SubClass();

// 這樣有缺點麼? 當然有,下面我們來通過例子來說明一下

這種繼承的方式的缺點、

var SuperClass = function(name) {
    var id = 1;
    this.name = name;
    this.todo = [1, 2, 3, 4];
    this.work = function() {
        console.log(this.name + 'in SuperClass');
    };
};
SuperClass.prototype.getSuperName = function() {
    return this.name;
};

var SubClass = function() {
    this.getSubName = function() {
        console.log('this is subname');
    };
};

SubClass.prototype = new SuperClass('superClass');
var sub = new SubClass();
sub.todo.push('subClass name');
var sub2 = new SubClass();
console.log(sub2.todo); // [ 1, 2, 3, 4, 'subClass name']
// 這裡是缺陷一,父類屬性會被例項子類修改、汙染

console.log(sub.name); //superClass
console.log(sub2.name); //superClass

// 子類的例項只能有一個name,這很顯然也是不夠靈活的,這裡就是缺陷二

這裡因為子類例項物件1,對於父類共有屬性進行了修改,導致子類例項物件2 的對應屬性受到了汙染。那有沒有什麼辦法可以避免這種汙染呢?當然是有的,後面我們會介紹到的。

5-1-2 通過建構函式繼承
// 宣告父類
function Animal(color) {
    this.name = 'animal';
    this.type = ['pig', 'cat'];
    this.color = color;
}

// 新增原型方法
Animal.prototype.eat = function(food) {
    console.log(food);
};

// 宣告子類
function Dog() {
    Animal.apply(this, arguments);
    // 這一步的操作就是改變 Animal 方法的上下文,然後讓 Dog 也具備了 父類建構函式內的屬性和方法
}

var dog1 = new Dog('blue'); // dog1.color -> blue
var dog2 = new Dog('red'); // dog2.color -> red

dog1.type.push('haha');
console.log(dog2.type); // [ 'pig', 'cat' ]

我沒看到 dog1 修改了繼承自父類的屬性 type ,但是 dog2 的 type 屬性併為被影響到。原因就是我們例項化的時候,建立的例項物件的指標指向的位置是不同的,所以對應的 __proto__ 指向的是 不同的子類建構函式的 prototype。可能會比較繞口,但是本質就是 new 操作生成了2個不同的物件,各自有各自的原型屬性,互不干擾。

但是上面也有一個缺陷就是,子類沒辦法繼承到父類原型上的方法和屬性

那聰明的前端開發者們,就想到了 集合前2者的優勢,進行了 組合式繼承。

5-1-3 組合式繼承
// 宣告父類
function Animal(color) {
    this.name = 'animal';
    this.type = ['pig', 'cat'];
    this.color = color;
}

// 新增原型方法
Animal.prototype.eat = function(food) {
    console.log(food);
};

// 宣告子類
function Dog() {
    Animal.apply(this, arguments);
    // 這一步的操作就是改變 Animal 方法的上下文,然後讓 Dog 也具備了
    // 父類建構函式內的屬性和方法
}
Dog.prototype = new Animal('Animal Color');

var dog1 = new Dog();
console.log((dog1.color = 'dog1.name'));
var dog2 = new Dog();

console.log(dog2.color); // undefined

這裡為什麼 dog2.color 是 undefined 而不是 'dog1.name' 呢?
因為,我們子類的建構函式,已經繼承了 父類的建構函式內部的屬性和方法,然後,在例項我們 子類的時候,子類的例項物件就會有先從本身的物件中去尋找 color 屬性。
當找到對應屬性的時候,無論是否有值,都會優先返回 例項化物件本身的屬性,而不再需要從原型鏈中查詢對應屬性。

5-2 ES6 中是如何實現 繼承的?

這裡我們想想繼承,到底是繼承什麼?如何繼承?為什麼要繼承?

5-2-1 ES6 的繼承方式
class Animal {
    constructor(name) {
        this.name = name;
    }
    eat(food) {
        console.log(`${this.name}吃${food}`);
    }
}

class Dog extends Animal {
    constructor(name) {
        super(name);
        this.name = name;
    }
    run() {
        console.log('小狗泡泡跑');
    }
}

let dog1 = new Dog('小狗');
let dog2 = new Dog('小花');
console.log(dog1.name); // 小狗
console.log(dog2.name); // 小花

dog1.__proto__ === Dog.prototype    // true
Dog.__proto__ === Animal            // true

這裡 Dog 的 __proto__ 指向的是 Animal 這個類

因為 Animal 這個類中的 constructor 就是原來的建構函式, 其中剩下的方法、屬性都是 prototype 上的公共方法與屬性。是可以被子類繼承

六、總結

這裡全篇文章又總結了下 JS 中繼承的原理以及一些我們平時可能忽略的問題,這裡就相當於在 學習 ts 之前,帶著大家再一起復習一下。好了,本篇文章就先到這裡了。


GitHub 地址:(歡迎 star 、歡迎推薦 : )
《前端之路》 - TypeScript(四)class 篇