面向对象
类class
我们先看一个使用类的例子:
class Student {
// 学生姓名
public readonly name: string;
// 学生班级
private clazz: number;
// 学生年龄
private age?: number;
// 构造器
constructor(name: string, clazz: number, age?: number) {
this.name = name;
this.age = age;
this.clazz = clazz;
}
// 方法
sayHello() {
console.log("Hello! My name is " + this.name);
}
}
let Alice = new Student("Alice", 1);
let Bob = new Student("Bob", 2, 18);
如果你使用过C#或Java,你会对这种语法非常熟悉。 我们声明一个Student
类。
这个类有3个属性:name, clazz, age
,一个构造器constructor
,一个方法sayHello()
在引用任何一个类成员的时候都用了this
。 它表示我们访问的是类的成员。
最后一行,使用new
构造了Student
类的一个实例。 它会调用之前定义的构造函数,创建一个Student
类型的新对象,并执行构造函数初始化它。
readonly修饰符
你可以使用readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。
class Student {
readonly name: string;
constructor (name: string) {
this.name = name;
}
}
let Bob = new Student("Bob");
Bob.name = "Alice"; // 错误! name 是只读的.
公共,私有与受保护的修饰符
默认为public
在上面的例子里,我们可以自由的访问程序里定义的成员。 如果你对其它语言中的类比较了解,就会注意到我们在之前的代码里并没有使用public
来做修饰;例如,C#要求必须明确地使用public
指定成员是可见的。 在TypeScript里,成员都默认为public
。
你也可以明确的将一个成员标记成public
。 我们可以用下面的方式来重写上面的Animal
类:
class Animal {
public name: string;
public constructor(name: string) { this.name = name; }
}
let dog = new Animal("dog");
console.log(dog.name); //输出dog
理解private
当成员被标记成private
时,它就不能在声明它的类的外部访问。比如:
class Animal {
private name: string;
constructor(name: string) { this.name = name; }
}
let dog = new Animal("dog");
console.log(dog.name); //错误,name是私有的
private
标记的成员在子类中也无法进行访问。比如:
class Animal {
private name: string;
constructor(name: string) {
this.name = name;
}
}
class dog extends Animal {
getName() {
// super调用父类元素
return super.name; //错误,private成员无法在子类调用
}
}
理解protected
protected
修饰符与private
修饰符的行为很相似,但有一点不同,protected
成员在子类中仍然可以访问。例如:
class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
class dog extends Animal {
getName(): string {
return super.name; // 正确
}
}
构造函数也可以被标记成protected
。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
class Animal {
protected name: string;
protected constructor(name: string) {
this.name = name;
}
}
class dog extends Animal {
constructor(name: string) {
super(name); // 调用父类构造器
}
getName(): string {
return super.name;
}
}
let new_cat = new Animal("cat"); // 错误
let new_dog = new dog("dog"); // 正确
存取器
TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问。
下面来看如何把一个简单的类改写成使用get
和set
。 首先,我们从一个没有使用存取器的例子开始。
class Student {
// 学生姓名
private _name: string;
constructor(_name: string) {
this._name = _name;
}
sayHello() {
console.log("Hello! My name is " + this._name);
}
set name(name: string) {
if (name) {
this._name = name;
} else {
throw new Error("name cannot be empty!!!");
}
}
get name() {
return this._name;
}
}
let Alice = new Student("Alice");
Alice.name = ""; // 将会报错Error("name cannot be empty!!!");
Alice.name = "Bob"; // 正确,调用set方法
console.log(Alice.name); // 调用get方法
允许随意设置name
虽然方便,但是我们仍想在设置name
强制执行某些约束。
在这个版本里,我们添加一个setter
来检查newName
的长度,以确保它满足数据库字段的最大长度限制。若它不满足,那么我们就抛一个错误来告诉客户端出错了。
为保留原有的功能,我们同时添加一个getter
用来读取name
。
首先,存取器要求你将编译器设置为输出
ECMAScript 5(ES5)
或更高。 不支持降级到ECMAScript 3(ES3)
。 其次,只带有get
不带有set
的存取器自动被推断为readonly
。 这在从代码生成.d.ts
文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。