- 基本示例
- 繼承
- 公共、私有與受保護(hù)的修飾符
- readonly 修飾符
- 存取器
- 靜態(tài)屬性
- 抽象類
- 高級(jí)技巧
1. 基本示例
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return 'Hello, ' + this.greeting;
}
}
let greeter = new Greeter('world');
console.log(greeter.greet());
2. 繼承
繼承時(shí)子類要擁有匹配的構(gòu)造器,super 關(guān)鍵字直接調(diào)用時(shí)等價(jià)于調(diào)用父類構(gòu)造函數(shù),而 super.props 可以訪問父對(duì)象上的方法成員。在創(chuàng)建子類實(shí)例時(shí),要先調(diào)用 super 來生成匹配的父類,并且必須在使用 this 關(guān)鍵字之前使用。 然后才進(jìn)一步給當(dāng)前子類自己加戲。
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
move(distance: number = 0) {
console.log(`${this.name} moved ${distance}`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distance: number = 5) {
console.log('蛇皮走位ing...');
super.move(distance);
}
}
class Horse extends Animal {
constructor(name: string) {
super(name);
}
move(distance: number = 45) {
console.log('奔騰ing...')
super.move(distance);
}
}
let tony = new Snake('Tony');
let tom: Animal = new Horse('Tom');
tony.move();
tom.move(75);
// 蛇皮走位ing...
// Tony moved 5
// 奔騰ing...
// Tom moved 75
3. 公共、私有與受保護(hù)的修飾符
成員默認(rèn)都是 public 公共的,可以在外部任意訪問。
3.1 private 私有修飾符
private 修飾的成員只能在類內(nèi)部訪問,private 修飾的構(gòu)造器也只有在內(nèi)部才能使用 new 操作符來實(shí)例化:
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
new Animal('Cat').name; // 報(bào)錯(cuò)
class Pig extends Animal {
constructor() {
super('Peng');
}
}
class Employee {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
// 私有成員來源不一樣,不能互相兼容,除非成員完全一樣
let animal = new Animal('name');
let pig = new Pig();
let employee = new Employee('Bob');
animal = pig; // 子類可以賦值給父類
animal = employee;
// 報(bào)錯(cuò),就算 Employee 看起來和 Animal 定義的一樣,但是因?yàn)榇嬖谟?private 修飾的各自私有屬性,所以兩個(gè)類不能等價(jià)
// 除非 name 都是 public,不再私有了,才能認(rèn)為所指的是同一個(gè)屬性
3.2 protected 受保護(hù)的修飾符
class Person {
protected name: string;
protected constructor(name: string) {
this.name = name;
}
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
getEmployeeInfo() {
new Person('wangpeng')
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let tony = new Employee('name', 'Sales');
console.log(tony.getEmployeeInfo());
// Hello, my name is name and I work in Sales
console.log(tony.name);
// 報(bào)錯(cuò),受保護(hù)的(protected)屬性 name 來自于超類 Person,只能在 Person 類中和其子類中訪問
let bob = new Person('peng');
// 報(bào)錯(cuò),同理,因?yàn)?Person 類的構(gòu)造器也是 protected
4. readonly 只讀修飾符
readonly 類似于是 Java 中的 final 關(guān)鍵字,用來修飾只能被賦值一次、之后只讀的值。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
// Person 類的構(gòu)造器中還可以使用參數(shù)屬性,把聲明和賦值合并為一個(gè)操作,但是不推薦使用
class Person {
constructor( readonly name: string) {
this.name = name;
}
}
let tom = new Person('Tom');
console.log(tom.name);
tom.name = 'Sam'; // 報(bào)錯(cuò),只讀屬性不能修改
5. 存取器
編譯的時(shí)候要指定為 ES5 或更高版本,方能支持訪問器。
編譯時(shí)指定 ES5 版本:
tsc index.ts --target es5
編譯成 ES5 后,實(shí)際上是利用了 Object.defineProperty() 來定義訪問器屬性描述符,這也叫訪問劫持(這些專業(yè)術(shù)語總是如此專業(yè)):
var password = 'daohaotiandaleipi';
var Employee = /** @class */ (function () {
function Employee() {
}
Object.defineProperty(Employee.prototype, "fullName", {
get: function () {
return this._fullName;
},
set: function (newName) {
if (password && password === 'daohaotiandaleipi') {
this._fullName = newName;
}
else {
console.log('Error: Unauthorized update of employee!');
}
},
enumerable: true,
configurable: true
});
return Employee;
}());
var employee = new Employee();
employee.fullName = 'Tony'; // 設(shè)置前時(shí)候會(huì)校驗(yàn)密碼
if (employee.fullName) {
console.log(employee.fullName);
}
下面指定編譯為 ES6 版本,和 TS 源碼區(qū)別不大,基本只是把類型系統(tǒng)移除,畢竟 ES6 的 class 本身就支持這樣的訪問器語法:
let password = 'daohaotiandaleipi';
class Employee {
get fullName() {
return this._fullName;
}
set fullName(newName) {
if (password && password === 'daohaotiandaleipi') {
this._fullName = newName;
}
else {
console.log('Error: Unauthorized update of employee!');
}
}
}
let employee = new Employee();
employee.fullName = 'Tony'; // 設(shè)置前時(shí)候會(huì)校驗(yàn)密碼
if (employee.fullName) {
console.log(employee.fullName);
}
6. 靜態(tài)屬性
靜態(tài)屬性存在類本身上,而不是實(shí)例上。類其實(shí)也是一個(gè)對(duì)象,使用 SomeClass.someProperty 這種形式來讀取靜態(tài)屬性。
class Grid {
static origin = { x: 0, y: 0 }; // 靜態(tài)屬性
scale: number; // 縮放比例
constructor(scale: number) {
this.scale = scale;
}
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;
}
}
let grid1 = new Grid(1.0);
console.log(grid1.calculateDistanceFromOrigin({ x: 3, y: 4 }));
let grid2 = new Grid(5.0);
console.log(grid2.calculateDistanceFromOrigin({ x: 3, y: 4 }));
7. 抽象類
抽象類作為其他派生類的基類,不能直接進(jìn)行實(shí)例化,由其派生類來實(shí)例化,抽象類可以提供抽象的方法簽名,也可以提供一些方法的默認(rèn)實(shí)現(xiàn)
abstract class Department {
name: string;
constructor(name: string) {
this.name = name;
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void;
}
class AccountingDepartment extends Department {
constructor() {
super('會(huì)計(jì)事務(wù)所');
}
printMeeting(): void {
console.log('每天十點(diǎn)開會(huì)');
}
generateReports(): void {
console.log('生成報(bào)告中...');
}
// printName(): void {
// console.log('Department name: ' + '我瞎編的部門');
// }
}
let department: Department;
department = new Department(); // 報(bào)錯(cuò),不能直接實(shí)例化抽象類
department = new AccountingDepartment();
department.printName();
department.printMeeting();
// 報(bào)錯(cuò),generateReports 并不存在于 Department 抽象類中,而是存在于 AccountingDepartment
// 除非把 department 類型聲明為 AccountingDepartment
department.generateReports();
8. 高級(jí)技巧
8.1 構(gòu)造函數(shù)
如之前了解的那樣,類具有靜態(tài)部分和實(shí)例部分:
class Greeter {
static standardGreeting = 'Hello, there';
greeting: string;
greet() {
if (this.greeting) {
return 'Hello, ' + this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}
let greeter1: Greeter; // 聲明 greeter1 是 Greeter 類的實(shí)例類型,也叫 Greeter
greeter1 = new Greeter();
console.log(greeter1.greet());
那如果想聲明一個(gè)變量let greeterMaker,將來會(huì)用來存儲(chǔ)一個(gè)類(在 JS 這門語言中其實(shí)就是構(gòu)造函數(shù)),那么 TS 中當(dāng)給該變量指定類型時(shí), let greeterMaker: Greeter 這樣是不行的,這意思曲解成了變量 greeterMaker 的類型是 Greeter 的實(shí)例類型。
參見下方代碼,此時(shí),可以使用 typeof Greeter 來表明這里我需要的是 Greeter 類本身的類型,或者說是 Greeter 構(gòu)造函數(shù)本身的類型(而不是其實(shí)例的類型),這個(gè)類型包含了類的所有靜態(tài)成員和構(gòu)造函數(shù)。
當(dāng)然如果是聲明變量的同時(shí)就進(jìn)行了賦值,那么即使不顯式地指出 greeterMaker 的類型,在類似 vscode 這種編輯器中 ,使用鼠標(biāo) hover 的時(shí)候也會(huì)顯示 TS 自動(dòng)推斷出的類型:

接著就可以 new greeterMaker()。
把下面代碼接著寫在上一段代碼后面:
let greeterMaker: typeof Greeter;
greeterMaker = Greeter;
greeterMaker.standardGreeting = 'Hey there';
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
console.log(greeter1.greet());
總的輸出結(jié)果如下,說明其實(shí) greeterMaker 和 Greeter 引用的都是同一個(gè)值
Hello, there
Hey hahah
Hey hahah
8.2 把類當(dāng)做接口使用
就像上一章提到的接口繼承類,雖然類能當(dāng)做接口使用,并且能被接口繼承,但通常不建議這樣做。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};