- 基本示例
- 继承
- 公共、私有与受保护的修饰符
- readonly 修饰符
- 存取器
- 静态属性
- 抽象类
- 高级技巧
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. 继承
继承时子类要拥有匹配的构造器,super
关键字直接调用时等价于调用父类构造函数,而 super.props
可以访问父对象上的方法成员。在创建子类实例时,要先调用 super
来生成匹配的父类,并且必须在使用 this
关键字之前使用。 然后才进一步给当前子类自己加戏。
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. 公共、私有与受保护的修饰符
成员默认都是 public
公共的,可以在外部任意访问。
3.1 private 私有修饰符
private
修饰的成员只能在类内部访问,private
修饰的构造器也只有在内部才能使用 new
操作符来实例化:
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
new Animal('Cat').name; // 报错
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;
// 报错,就算 Employee 看起来和 Animal 定义的一样,但是因为存在用 private 修饰的各自私有属性,所以两个类不能等价
// 除非 name 都是 public,不再私有了,才能认为所指的是同一个属性
3.2 protected 受保护的修饰符
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);
// 报错,受保护的(protected)属性 name 来自于超类 Person,只能在 Person 类中和其子类中访问
let bob = new Person('peng');
// 报错,同理,因为 Person 类的构造器也是 protected
4. readonly 只读修饰符
readonly
类似于是 Java 中的 final
关键字,用来修饰只能被赋值一次、之后只读的值。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name;
}
}
// Person 类的构造器中还可以使用参数属性,把声明和赋值合并为一个操作,但是不推荐使用
class Person {
constructor( readonly name: string) {
this.name = name;
}
}
let tom = new Person('Tom');
console.log(tom.name);
tom.name = 'Sam'; // 报错,只读属性不能修改
5. 存取器
编译的时候要指定为 ES5 或更高版本,方能支持访问器。
编译时指定 ES5 版本:
tsc index.ts --target es5
编译成 ES5 后,实际上是利用了 Object.defineProperty()
来定义访问器属性描述符,这也叫访问劫持(这些专业术语总是如此专业):
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'; // 设置前时候会校验密码
if (employee.fullName) {
console.log(employee.fullName);
}
下面指定编译为 ES6 版本,和 TS 源码区别不大,基本只是把类型系统移除,毕竟 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'; // 设置前时候会校验密码
if (employee.fullName) {
console.log(employee.fullName);
}
6. 静态属性
静态属性存在类本身上,而不是实例上。类其实也是一个对象,使用 SomeClass.someProperty
这种形式来读取静态属性。
class Grid {
static origin = { x: 0, y: 0 }; // 静态属性
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. 抽象类
抽象类作为其他派生类的基类,不能直接进行实例化,由其派生类来实例化,抽象类可以提供抽象的方法签名,也可以提供一些方法的默认实现
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('会计事务所');
}
printMeeting(): void {
console.log('每天十点开会');
}
generateReports(): void {
console.log('生成报告中...');
}
// printName(): void {
// console.log('Department name: ' + '我瞎编的部门');
// }
}
let department: Department;
department = new Department(); // 报错,不能直接实例化抽象类
department = new AccountingDepartment();
department.printName();
department.printMeeting();
// 报错,generateReports 并不存在于 Department 抽象类中,而是存在于 AccountingDepartment
// 除非把 department 类型声明为 AccountingDepartment
department.generateReports();
8. 高级技巧
8.1 构造函数
如之前了解的那样,类具有静态部分和实例部分:
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 类的实例类型,也叫 Greeter
greeter1 = new Greeter();
console.log(greeter1.greet());
那如果想声明一个变量let greeterMaker
,将来会用来存储一个类(在 JS 这门语言中其实就是构造函数),那么 TS 中当给该变量指定类型时, let greeterMaker: Greeter
这样是不行的,这意思曲解成了变量 greeterMaker
的类型是 Greeter
的实例类型。
参见下方代码,此时,可以使用 typeof Greeter
来表明这里我需要的是 Greeter 类本身的类型,或者说是 Greeter
构造函数本身的类型(而不是其实例的类型),这个类型包含了类的所有静态成员和构造函数。
当然如果是声明变量的同时就进行了赋值,那么即使不显式地指出 greeterMaker
的类型,在类似 vscode 这种编辑器中 ,使用鼠标 hover 的时候也会显示 TS 自动推断出的类型:
接着就可以 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());
总的输出结果如下,说明其实 greeterMaker
和 Greeter
引用的都是同一个值
Hello, there
Hey hahah
Hey hahah
8.2 把类当做接口使用
就像上一章提到的接口继承类,虽然类能当做接口使用,并且能被接口继承,但通常不建议这样做。
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
网友评论