介绍
1. 类介绍
传统的JavaScript通过函数和基于原型的继承来创建可重用的组件,从ES6开始,JavaScript程序员也可以使用面向对象的方法来创建对象。例如,下列通过class
关键词,来声明了一个类:Greeter
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
通过使用new Greeter()
可以创建很多个基于该Greeter
类的实例。如let greeter = new Greeter("world");
,就创建了一个该类的实例greeter。
2. 类继承
在基于类的程序设计中,一种最基本的模式是:允许通过继承来实现类的扩展。如下面的例子,我们首先定义了一个Animal的类,然后又定义了Dog类并扩展了Animal,此时通过Dog类创建的实例就自动继承了Animal中的方法和属性:
class Animal {
move(distanceInMeters) {
console.log(`Animal moved ${distanceInMeters}m.`)
}
}
class Dog extends Animal {
bark() {
console.log('Woof! Woof!')
}
}
const dog = new Dog()
dog.bark() // Woof! Woof!
dog.move(10) // Animal moved 10m.
dog.bark() // Woof! Woof!
Typescript的继承中一个最重要的关键字:super
。例如下面的例子,我们首先定义了Animal
类,然后使用extends
关键词创建了Animal
的两个子类:Horse
和Snake
:
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)
}
}
class Horse extends Animal {
constructor(name: string) {
super(name)
}
move(distanceInMeters = 45) {
console.log('Galloping...')
super.move(distanceInMeters)
}
}
let sam = new Snake('Sammy the Python')
let tom: Animal = new Horse('Tommy the Palomino')
sam.move() // Slithering... Sammy the Python moved 5m.
tom.move(34) // Galloping... Tommy the Palomino moved 34m.
与前面不同的是:
- 派生类Snake和Horse都包含了一个构造函数,它必须调用
super
才行,它会执行基类里的构造函数。而且在构造函数里访问this属性之前,也一定要调用super
——这是typescript强制执行的一条重要原则 - 该示例演示了如何重写父类的方法:Snake和Horse都创建了move方法,它们重写了冲Animal继承来的move方法,使得move在不同的类里有不同的功能。注意:即使tom被声明为Animal类,但因为它的值被声明为了Horse,调用.move方法时,还是执行的Horse里面的方法。
所以,上述方法中,调用sam.move()
和tom.move(34)
的输出分别为:
sam.move()
// Slithering...
// Sammy the Python moved 5m.
tom.move(34)
// Galloping...
// Tommy the Palomino moved 34m.
这是Typescript里面关于类的一个高级用法,在创建一些复杂类的时候可能会使用到,需要掌握。
3. 修饰符
3.1 public(默认)
在Typescript里,成员默认都是public
,不过你开业可以将一个成员标记成明确的public类。如下面的示例:
class Animal {
public name: string
public constructor(theName: string) {
this.name = theName
}
public move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`)
}
}
将Animal类里的属性全都标记成public,与前面不标记时的结果一样。
3.2 private
当成员被声明为private
,它就不能被声明它的函数的外部访问。如:将Animal类中的name声明为private
class Animal {
private name: string
public constructor(theName: string) {
this.name = theName
}
}
使用new Animal("Cat").name
方法访问name就会报错,错误为:提示name是私有化属性。
3.3 protected
protected
和private
修饰符优点相似,不同的是:protected
修饰符在派生类中也可以访问。如下图中,当我们在Person类中定义了protected
类型的属性,在派生的howard示例中访问name,就会提示该属性是protectd无法被访问,而访问private
类型的department则无提醒。
另外,构造函数也可以被声明为protected
,这意味着这个类不能在包含它的类外被实例化,但是能被继承。
3.4 readonly
只读属性,必须在声明时或构造函数里被初始化。如:我们定义了readonly属性,就无法在实例里面赋值。
4. 存取器
存取器指的就是typescript的get/set方法,它可以方便我们修改类中的属性。如,下列代码定义了一个修改名称的方法,在修改名称时还验证了passcode是否正确,如果正确则修改成功,否则不做修改。
let passcode = 'secert passcode'
class Employee {
private _fullName: string
get fullName(): string {
return this._fullName
}
set fullName(newName: string) {
if(passcode && passcode === 'secert passcode') {
this._fullName = newName
} else {
console.log('Error: Unauthorized update of employee')
}
}
}
当我们创建一个基于Employee
的实例,并且对它修改名称和获取名称:
let employee = new Employee()
employee.fullName = 'Bolb Smith'
if(employee.fullName) {
alert(employee.fullName)
}
需要注意:
- 存取器要求将编译器设置为输出ECMAScript 5或更高
- 只带有get不带有set的存取器自动被推断为readonly
- 使用存取器在从代码生成.d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
5. 静态属性
typescript除了可以定义实例属性,还可以定义静态属性。静态属性存在于类本身上面而不是类的实例上,如在下面的例子中,在我们使用static定义origin,因为它是所有网格都会用到的属性。 每个实例想要访问这个属性的时候,都要在origin前面加上类名。 如同在实例属性上使用this.前缀来访问属性一样,这里我们使用Grid.来访问静态属性。
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}))
6. 抽象类
抽象类通过关键词abstract
来定义。抽象类中的抽象方法不包含具体实现而且必须在派生类中实现。如下列的代码,我们定义了一个抽象类,并且在派生中实现了抽象类中的方法:
可以看到:
- 如果要继承抽象类,则抽象类中定义的方法一定要在实例中实现
- 不能直接创建一个抽象类的实例
- 声明成抽象类的实例,无法使用实例中的方法
高级技巧
1. 构造函数
在TypeScript里声明一个类的时候,实际上同时声明了很多东西。 首先就是类的实例的类型,如我们可以写let greeter: Greeter
,这其实就是说Greeter类的实例的类型是Greeter。这个其实很好懂,了解JavaScript中的原型继承就能很好理解这个概念。
2. 把类当接口使用
如前所述,类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类。
class Point {
x: number
y: number
}
interface Point3d extends Point {
z: number
}
let point3d: Point3d = { x: 1, y: 2, z: 3 }