日期: 2019 年 9 月2 日
类
类的例子
/**
* @description 类
* @class Greeter
*/
class Greeter{
greeting: string;
constructor(message: string){
this.greeting = message;
}
greet(){
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
console.log(greeter.greet());
继承
基于类的程序设计中一种最基本的模式是允许使用继承来扩展现有的类
/**
* @description 类的继承
* @class Animal
*/
class Animal{
move(distance: number= 0): void{
console.log(`Animal moved ${ distance } meters.`)
}
}
class Dog extends Animal{
bark(){
console.log("Woof,wang wang...");
}
}
let dog = new Dog();
dog.bark();
dog.move();
dog.move(10);
这个例子展示了最基本的继承:类从基类中继承了属性和方法。 这里, Dog是一个 派生类,它派生自 Animal 基类,通过 extends关键字。 派生类通常被称作 子类,基类通常被称作 超类
一个更加复杂的类继承的例子:
/**
* @description 复杂的类继承的例子
* @class Animals
*/
class Animals{
name: string;
constructor(myName: string){
this.name = myName;
}
move(distance: number= 22){
console.log(`${this.name} moved ${ distance } meters.`);
}
}
class Horse extends Animals{
constructor(name: string){
super(name);
}
move(distance: number= 233){
console.log('Horse, horse..');
super.move(distance);
}
}
class Cat extends Animals{
constructor(name: string){
super(name);
}
move(distance: number= 23){
console.log("喵喵喵...");
super.move(distance);
}
}
let horse = new Horse("black Horse");
horse.move();
horse.move(222);
let cat = new Cat("little cat");
cat.move();
cat.move(123);
这个例子演示了如何在子类里可以重写父类的方法
公有、私有与受保护的修饰符
默认为 public
在TypeScript里,成员都默认为 public。你也可以明确的将一个成员标记成 public
/**
* @description typescript 中,成员默认都是 public
* @class Haha
*/
class Haha{
public name: string;
public constructor(theName: string){
this.name = theName;
}
public greet(){
console.log(`Hello, ${ this.name }`);
}
}
let haha = new Haha("Lee");
console.log(haha.name);
私有 private
当成员被标记成 private时,它就不能在声明它的类的外部访问
/**
* @description 当成员被标记成 private 时,就不能在声明它的类外访问它
* @class Hahah
*/
class Hahah{
private name: string;
constructor(theName: string){
this.name = theName;
}
printName(){
console.log(this.name);
}
}
class Hah extends Hahah{
constructor(name: string){
super(name);
}
// sayHello(){ // error
// console.log(`Hello, ${ this.name }`)
// }
}
let ha = new Hahah('hello');
ha.printName();
// ha.name; // error
TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的,当我们比较带有 private或 protected成员的类型的时候,情况就不同了。 如果其中一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的。 对于 protected成员也使用这个规则。
/**
* @description 如果其中一个类型里包含一个 private成员,
* 那么只有当另外一个类型中也存在这样一个 private成员,
* 并且它们都是来自同一处声明时,我们才认为这两个类型是兼容的
* @class Fruit
*/
class Fruit{
private name: string;
constructor(theName: string){
this.name = theName;
}
}
class Apple extends Fruit{
constructor(){
super("apple");
}
}
class Employe{
private name: string;
constructor(theName: string){
this.name = theName;
}
}
let fruit = new Fruit("pine-apple");
let apple = new Apple();
let employe = new Employe("Tom");
fruit = apple;
// fruit = employe; // error
protected
protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问。例如:
/**
* @description 当成员被标记成 protected 时,只能在类及其子类中访问,在此之外不能访问
* @class Person
*/
class Person{
protected name: string;
constructor(theName: string){
this.name = theName;
}
}
class Staff extends Person{
private dpt: string;
constructor(name: string, dpt: string){
super(name);
this.dpt = dpt;
}
greet(){
console.log(`Hello, my name is ${ this.name } and I work in ${ this.dpt }.`);
}
}
let hand = new Staff("hanpi", "technology");
hand.greet;
// console.log(hand. name); // error
readonly 修饰符
你可以使用 readonly关键字将属性设置为只读的,只读属性必须在声明时或构造函数里被初始化
/**
* @description readonly 修饰符
* @class Octopus
*/
class Octopus{
readonly name: string;
constructor(theName: string){
this.name = theName;
}
}
let octo = new Octopus("yaya");
// octo.name = "lala"; // error
参数属性
参数属性通过给构造函数参数前面添加一个访问限定符来声明。 使用 private限定一个参数属性会声明并初始化一个私有成员;对于 public和 protected来说也是一样
/**
* @description 参数属性
* @class Octo
*/
class Octo{
constructor(readonly name: string){ }
}
let a = new Octo("lala");
存取器
TypeScript支持通过getters/setters来截取对对象成员的访问。 它能帮助你有效的控制对对象成员的访问
let passcode = "secret passcode";
/**
* @description 存取器
* @class Employes
*/
class Employes{
private _fullName: string = "lalala";
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 Employes.");
}
}
}
let man = new Employes();
console.log(man.fullName);
man.fullName = "hahah";
console.log(man.fullName);
对于存取器有下面几点需要注意的:首先,存取器要求你将编译器设置为输出ECMAScript 5或更高。 不支持降级到ECMAScript 3。 其次,只带有 get不带有 set的存取器自动被推断为 readonly。 这在从代码生成 .d.ts文件时是有帮助的,因为利用这个属性的用户会看到不允许够改变它的值。
静态属性
到目前为止,我们只讨论了类的实例成员,那些仅当类被实例化的时候才会被初始化的属性。 我们也可以创建类的静态成员,这些属性存在于类本身上面而不是类的实例上
/**
* @description 静态属性, 类的静态成员,这些属性存在于类本身上面而不是类的实例上
* @class Grid
*/
class Grid{
static origin= { x: 0, y: 0 };
calculateDistance( point: { x: number, y: number }){
let xDis = point.x - Grid.origin.x;
let yDis = point.y - Grid.origin.y;
return Math.sqrt(xDis*xDis + yDis*yDis) / this.scale;
}
constructor(public scale: number){}
}
let grid1 = new Grid(1);
console.log(grid1.calculateDistance({ x: 10, y: 10 }));
抽象类
抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法
抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
/**
* @description 抽象类
* @abstract
* @class A
*/
abstract class A{
constructor(public name: string){};
printName(): void{
console.log(`Name is: ${ this.name }.`);
}
abstract printMeeting(): void; // 必须在派生类中实现
}
class B extends A{
constructor(){
super("Lyli");
}
printMeeting(): void{
console.log("meeeting");
}
printHaha(): void{
console.log("heihei..");
}
}
let ah : A;
// ah = new A(); // error, 无法创建抽象类的实例
ah = new B();
ah.printName();
ah.printMeeting();
// ah.printHaha(); // 方法在 A 上不存在
高级技巧
构造函数
当你在TypeScript里声明了一个类的时候,实际上同时声明了很多东西。 首先就是类的 实例的类型
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
这里,我们写了 let greeter: Greeter,意思是 Greeter类的实例的类型是 Greeter。 这对于用过其它面向对象语言的程序员来讲已经是老习惯了。
我们也创建了一个叫做 构造函数的值。 这个函数会在我们使用 new创建类实例的时候被调用。 下面我们来看看,上面的代码被编译成JavaScript后是什么样子的:
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
上面的代码里, let Greeter将被赋值为构造函数。 当我们调用 new并执行了这个函数后,便会得到一个类的实例。 这个构造函数也包含了类的所有静态属性。 换个角度说,我们可以认为类具有 实例部分与 静态部分这两个部分。
把类当接口使用
如上一节里所讲的,类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类
/**
* @description 把类当接口使用
* @class Point
*/
class Point{
x: number;
y: number;
}
interface Ponit3d extends Point{
z: number;
}
let point3d: Ponit3d = { x: 1, y: 2, z: 3 };
网友评论