一、js和ts類的區別
js是基于類的面向對象方式,構建父子類的繼承結構,寫起來不簡潔,也不形象。ts則是以簡潔明了的方式實現類的定義、繼承、擴展等等。
之前我也提過,js最終會登錄瀏覽器平臺,但是至少不是現在,所以在下一個js時代之前,我們需要定ts來規范當前的客戶端代碼。
二、用ts實現類
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
三、繼承
使用extends 關鍵字來繼承。
class Animal { // Animal是基類,也叫超類
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
class Dog extends Animal { // Dog是派生自Animal的派生類,也叫子類
bark() {
console.log('Woof! Woof!');
}
}
const dog = new Dog();
dog.bark();
dog.move(10);
派生類的構造函數必須包含 "super" 調用。這是ts強制執行的一條重要規則。
// 如果父類下沒有構造函數,如下
class Animal {
move(distanceInMeters: number = 0) {
}
}
class Snake extends Animal {
constructor(name: string) {
super() // 如果父類沒有定義構造函數,父類會有默認值,什么都不做,但是子類必須要在構造函數中調用super
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
console.log(sam);
// 父類含有構造函數
class Animal {
name: string;
constructor(theName: string) { // 在子類中執行基類的構造函數
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
四、公共、私有與受保護的修飾符
public
前置定義,ts里可以不寫即為默認為public,可以自由的訪問定義的值。
private
不能再聲明它的類的外部被訪問。即為私有屬性。
protected
protected修飾符與 private修飾符的行為很相似,但有一點不同, protected成員在派生類中仍然可以訪問,但是注意:依然不能在類的外面被訪問。
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); // 錯誤
構造函數也可以被標記成 protected。 這意味著這個類不能在包含它的類外被實例化,但是能被繼承。
// 類屬性 name、constructor => protected
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"); // 類“Person”的構造函數是受保護的,僅可在類聲明中訪問。
// 類屬性 name => not protected, constructor => protected
class Person {
protected name: string;
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");
console.log(john.name); // 屬性“name”受保護,只能在類“Person”及其子類中訪問。ts(2445)
五、readonly修飾符
使用 readonly關鍵字將屬性設置為只讀的。 只讀屬性必須在聲明時或構造函數里被初始化。
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"; // 錯誤! name 是只讀的.
參數屬性:參數屬性可以方便地讓我們在一個地方定義并初始化一個成員。
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) { // 聲明并初始化一個私有成員
}
}
// 等價于
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
六、存取器
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();
console.log(employee.fullName); // Property '_fullName' has no initializer and is not definitely assigned in the constructor.
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
注意:
- 存取器要求你將編譯器設置為輸出ECMAScript 5或更高。 不支持降級到ECMAScript 3。
- 其次,只帶有
get不帶有set的存取器自動被推斷為readonly。
七、靜態屬性
這些屬性存在于類本身上面而不是類的實例上。
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x); // 如同在實例屬性上使用 this.前綴來訪問屬性一樣,這里我們使用 Grid.來訪問靜態屬性。
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
八、抽象類
抽象類做為其它派生類的基類使用。 它們一般不會直接被實例化。
不同于接口,抽象類可以包含成員的實現細節。 abstract關鍵字是用于定義抽象類和在抽象類內部定義抽象方法。
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log('roaming the earch...');
}
}
抽象類中的抽象方法不包含具體實現并且必須在派生類中實現。 抽象方法的語法與接口方法相似。 兩者都是定義方法簽名但不包含方法體。 然而,抽象方法必須包含 abstract關鍵字并且可以包含訪問修飾符。
abstract class Department {
constructor(public name: string, age: number = 10) { // 如果age這里不用public定義,則它是一個局部變量,不能從實例的this.上獲取到
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必須在派生類中實現
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // 在派生類的構造函數中必須調用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // 允許創建一個對抽象類型的引用
// department = new Department(); // 錯誤: 不能創建一個抽象類的實例
department = new AccountingDepartment(); // 允許對一個抽象子類進行實例化和賦值
console.log(department.name); // Accounting and Auditing
console.log(department.age); // 類型“Department”上不存在屬性“age”。
department.printName();
department.printMeeting();
// department.generateReports(); // 錯誤: 方法在聲明的抽象類中不存在
注意上面標記*處: Typescript 提供的簡寫形式 — 用構造函數的參數直接定義屬性。
九、高級技巧
-
構造函數
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; // Greeter類的類型,而不是實例的類型。 greeterMaker.standardGreeting = "Hey there!"; let greeter2: Greeter = new greeterMaker(); console.log(greeter2.greet()); -
把類當作接口
因為類可以創建出類型,所以你能夠在允許使用接口的地方使用類。
簡而言之,就是把類當成接口來用。
class Point { x: number; y: number; } interface Point3d extends Point { z: number; } let point3d: Point3d = {x: 1, y: 2, z: 3};
浙公網安備 33010602011771號