TS筆記之 類(lèi)

類(lèi)

介紹

傳統(tǒng)的 JavaScript 程序使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但對(duì)于熟悉使用面向?qū)ο蠓绞降某绦騿T來(lái)講就有些棘手,因?yàn)樗麄冇玫氖腔陬?lèi)的繼承并且對(duì)象是由類(lèi)構(gòu)建出來(lái)的。 從 ECMAScript 2015,也就是 ECMAScript 6 開(kāi)始,JavaScript 程序員將能夠使用基于類(lèi)的面向?qū)ο蟮姆绞健?使用 TypeScript,我們?cè)试S開(kāi)發(fā)者現(xiàn)在就使用這些特性,并且編譯后的 JavaScript 可以在所有主流瀏覽器和平臺(tái)上運(yùn)行,而不需要等到下個(gè) JavaScript 版本。

類(lèi)

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");

繼承

class Animal {
  move(distanceInMeters: number = 0) {
    console.log(`Animal moved ${distanceInMeters}m.`);
  }
}

class Dog extends Animal {
  bark() {
    console.log("Woof! Woof!");
  }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();

這個(gè)例子展示了最基本的繼承:類(lèi)從基類(lèi)中繼承了屬性和方法。 這里, Dog 是一個(gè) 派生類(lèi),它派生自 Animal 基類(lèi),通過(guò) extends 關(guān)鍵字。 派生類(lèi)通常被稱(chēng)作 子類(lèi),基類(lèi)通常被稱(chēng)作 超類(lèi)。

公共,私有與受保護(hù)的修飾符

默認(rèn)為 \color{#BF414A}{public}

在 TypeScript 里,成員都默認(rèn)為 public。你也可以明確的將一個(gè)成員標(biāo)記成 public。

class Animal {
  public name: string;
  public constructor(theName: string) {
    this.name = theName;
  }
  public move(distanceInMeters: number) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

理解 \color{#BF414A}{private }

當(dāng)成員被標(biāo)記成 private 時(shí),它就不能在聲明它的類(lèi)的外部訪問(wèn)

class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal("Cat").name; // 錯(cuò)誤: 'name' 是私有的.

理解 \color{#BF414A}{protected }

protected 修飾符與 private 修飾符的行為很相似,但有一點(diǎn)不同, protected 成員在派生類(lèi)中仍然可以訪問(wèn)

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

class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 錯(cuò)誤

注意,我們不能在 Person 類(lèi)外使用 name,但是我們?nèi)匀豢梢酝ㄟ^(guò) Employee 類(lèi)的實(shí)例方法訪問(wèn),因?yàn)?Employee 是由 Person 派生而來(lái)的.

構(gòu)造函數(shù)也可以被標(biāo)記成 protected。 這意味著這個(gè)類(lèi)不能在包含它的類(lèi)外被實(shí)例化,但是能被繼承

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

// Employee 能夠繼承 Person
class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 錯(cuò)誤: 'Person' 的構(gòu)造函數(shù)是被保護(hù)的.

readonly 修飾符

你可以使用 readonly 關(guān)鍵字將屬性設(shè)置為只讀的。 只讀屬性必須在聲明時(shí)或構(gòu)造函數(shù)里被初始化。

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor(theName: string) {
    this.name = theName;
  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 錯(cuò)誤! name 是只讀的.

參數(shù)屬性

注意看我們是如何舍棄了 theName,僅在構(gòu)造函數(shù)里使用 readonly name: string 參數(shù)來(lái)創(chuàng)建和初始化 name 成員。 我們把聲明和賦值合并至一處

class Octopus {
  readonly numberOfLegs: number = 8;
  constructor(readonly name: string) {}
}

參數(shù)屬性通過(guò)給構(gòu)造函數(shù)參數(shù)前面添加一個(gè)訪問(wèn)限定符來(lái)聲明。 使用 private 限定一個(gè)參數(shù)屬性會(huì)聲明并初始化一個(gè)私有成員;對(duì)于 publicprotected 來(lái)說(shuō)也是一樣。

存取器

TypeScript 支持通過(guò) getters/setters 來(lái)截取對(duì)對(duì)象成員的訪問(wèn)。

let passcode = "secret passcode";

class Employee {
  private _fullName: string;

  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (passcode && passcode == "secret passcode") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
  alert(employee.fullName);
}

注意 只帶有 get 不帶有 set 的存取器自動(dòng)被推斷為 readonly

靜態(tài)屬性

實(shí)例成員:僅當(dāng)類(lèi)被實(shí)例化的時(shí)候才會(huì)被初始化的屬性

靜態(tài)成員:存在于類(lèi)本身上面而不是類(lèi)的實(shí)例上,關(guān)鍵字 <font color="#BF414A">static</font>

class Grid {
  static origin = { x: 0, y: 0 };
  calculateDistanceFromOrigin(point: { x: number; y: number }) {
    let xDist = point.x - Grid.origin.x;
    let yDist = point.y - Grid.origin.y;
    return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }
  constructor(public scale: number) {}
}

let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale

console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));

抽象類(lèi)

抽象類(lèi)做為其它派生類(lèi)的基類(lèi)使用。 它們一般不會(huì)直接被實(shí)例化。 不同于接口,抽象類(lèi)可以包含成員的實(shí)現(xiàn)細(xì)節(jié)。 \color{#bf414a}{abstract} 關(guān)鍵字是用于定義抽象類(lèi)和在抽象類(lèi)內(nèi)部定義抽象方法。

abstract class Animal {
  abstract makeSound(): void;
  move(): void {
    console.log("roaming the earch...");
  }
}

抽象類(lèi)中的抽象方法不包含具體實(shí)現(xiàn)并且必須在派生類(lèi)中實(shí)現(xiàn)。 抽象方法的語(yǔ)法與接口方法相似。 兩者都是定義方法簽名但不包含方法體。

abstract class Department {
  constructor(public name: string) {}

  printName(): void {
    console.log("Department name: " + this.name);
  }

  abstract printMeeting(): void; // 必須在派生類(lèi)中實(shí)現(xiàn)
}

class AccountingDepartment extends Department {
  constructor() {
    super("Accounting and Auditing"); // 在派生類(lèi)的構(gòu)造函數(shù)中必須調(diào)用 super()
  }

  printMeeting(): void {
    console.log("The Accounting Department meets each Monday at 10am.");
  }

  generateReports(): void {
    console.log("Generating accounting reports...");
  }
}

let department: Department; // 允許創(chuàng)建一個(gè)對(duì)抽象類(lèi)型的引用
department = new Department(); // 錯(cuò)誤: 不能創(chuàng)建一個(gè)抽象類(lèi)的實(shí)例
department = new AccountingDepartment(); // 允許對(duì)一個(gè)抽象子類(lèi)進(jìn)行實(shí)例化和賦值
department.printName();
department.printMeeting();
department.generateReports(); // 錯(cuò)誤: 方法在聲明的抽象類(lèi)中不存在

高級(jí)技巧

構(gòu)造函數(shù)

當(dāng)你在 TypeScript 里聲明了一個(gè)類(lèi)的時(shí)候,實(shí)際上同時(shí)聲明了很多東西

1.類(lèi)的 實(shí)例 的類(lèi)型。

我們寫(xiě)了 let greeter: Greeter,意思是 Greeter 類(lèi)的實(shí)例的類(lèi)型是 Greeter

2.我們也創(chuàng)建了一個(gè)叫做 構(gòu)造函數(shù) 的值。

let Greeter = (function () {
  function Greeter(message) {
    this.greeting = message;
  }
  Greeter.prototype.greet = function () {
    return "Hello, " + this.greeting;
  };
  return Greeter;
})();

let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());

上面的代碼里, let Greeter 將被賦值為構(gòu)造函數(shù)。 當(dāng)我們調(diào)用 new 并執(zhí)行了這個(gè)函數(shù)后,便會(huì)得到一個(gè)類(lèi)的實(shí)例。 這個(gè)構(gòu)造函數(shù)也包含了類(lèi)的所有靜態(tài)屬性。 換個(gè)角度說(shuō),我們可以認(rèn)為類(lèi)具有 實(shí)例部分與 靜態(tài)部分這兩個(gè)部分。

讓我們稍微改寫(xiě)一下這個(gè)例子

class Greeter {
  static standardGreeting = "Hello, there";
  greeting: string;
  greet() {
    if (this.greeting) {
      return "Hello, " + this.greeting;
    } else {
      return Greeter.standardGreeting;
    }
  }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

這個(gè)例子里, greeter1 與之前看到的一樣。 我們實(shí)例化 Greeter 類(lèi),并使用這個(gè)對(duì)象。 與我們之前看到的一樣。

再之后,我們直接使用類(lèi)。 我們創(chuàng)建了一個(gè)叫做 greeterMaker 的變量。 這個(gè)變量保存了這個(gè)類(lèi)或者說(shuō)保存了類(lèi)構(gòu)造函數(shù)。 然后我們使用 typeof Greeter,意思是取 Greeter 類(lèi)的類(lèi)型,而不是實(shí)例的類(lèi)型。 或者更確切的說(shuō),"告訴我 Greeter 標(biāo)識(shí)符的類(lèi)型",也就是構(gòu)造函數(shù)的類(lèi)型。 這個(gè)類(lèi)型包含了類(lèi)的所有靜態(tài)成員和構(gòu)造函數(shù)。 之后,就和前面一樣,我們?cè)?greeterMaker 上使用 new,創(chuàng)建 Greeter 的實(shí)例

把類(lèi)當(dāng)做接口使用

如上一節(jié)里所講的,類(lèi)定義會(huì)創(chuàng)建兩個(gè)東西:類(lèi)的實(shí)例類(lèi)型和一個(gè)構(gòu)造函數(shù)。 因?yàn)轭?lèi)可以創(chuàng)建出類(lèi)型,所以你能夠在允許使用接口的地方使用類(lèi)。

class Point {
  x: number;
  y: number;
}

interface Point3d extends Point {
  z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3 };
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 傳統(tǒng)的JavaScript程序使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但對(duì)于熟悉使用面向?qū)ο蠓绞降某绦騿T來(lái)講就...
    2o壹9閱讀 632評(píng)論 0 50
  • 介紹 傳統(tǒng)的JavaScript程序使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但對(duì)于熟悉使用面向?qū)ο蠓绞降某绦騿T...
    ERIC_s閱讀 179評(píng)論 0 0
  • 類(lèi) 對(duì)于傳統(tǒng)的 JavaScript 程序我們會(huì)使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但對(duì)于熟悉使用面向?qū)ο?..
    羅彬727閱讀 246評(píng)論 0 0
  • 介紹 傳統(tǒng)的 JavaScript 程序使用函數(shù)和基于原型的繼承來(lái)創(chuàng)建可重用的組件,但對(duì)于熟悉使用面向?qū)ο蠓绞降某?..
    24KBING閱讀 336評(píng)論 0 1
  • 下面看一個(gè)使用類(lèi)的例子: 我們聲明一個(gè)Greeter類(lèi)。這個(gè)類(lèi)有3個(gè)成員:一個(gè)叫做greeting的屬性,一個(gè)構(gòu)造...
    oWSQo閱讀 451評(píng)論 0 1

友情鏈接更多精彩內(nèi)容