TypeScript类继承

继承是面向对象程序设计的三个基本特征之一,TypeScript中的类也支持继承。在定义类时可以使用extends关键字来指定要继承的类,具体语法如下所示:

class DerivedClass extends BaseClass { }

在该语法中,我们将BaseClass叫作基类,将DerivedClass叫作派生类,派生类继承了基类。有时候,我们也将基类称作父类,将派生类称作子类

当派生类继承了基类后,就自动继承了基类的非私有成员

例如,下例中Circle类继承了Shape类。因此,Circle类获得了Shape类的colorswitchColor公有成员。我们可以在Circle类的实例对象上访问color成员变量和调用switchColor成员函数。示例如下:

class Shape {
   color: string = 'black';

   switchColor() {
       this.color =
           this.color === 'black' ? 'white' : 'black';
   }
}

class Circle extends Shape {}

const circle = new Circle();

circle.color; // 'black'
circle.switchColor();
circle.color; // 'white'

重写基类成员

在派生类中可以重写基类的成员变量和成员函数。在重写成员变量和成员函数时,需要在派生类中定义与基类中同名的成员变量和成员函数。示例如下:

class Shape {
   color: string = 'black';

   switchColor() {
       this.color =
           this.color === 'black' ? 'white' : 'black';
   }
}

class Circle extends Shape {
   color: string = 'red';

   switchColor() {
       this.color = this.color === 'red' ? 'green' : 'red';
   }
}

const circle = new Circle();

circle.color; // 'red'
circle.switchColor();
circle.color; // 'green'

此例中,Circle类重写了Shape类中的color属性和switchColor方法。第20行,读取circle对象上的color属性时,获取的是重写后的属性。第21行,调用circle对象上的switchColor方法时,调用的是重写后的方法。

在派生类中,可以通过super关键字来访问基类中的非私有成员。当派生类和基类中存在同名的非私有成员时,在派生类中只能通过super关键字来访问基类中的非私有成员,无法使用this关键字来引用基类中的非私有成员。示例如下:

class Shape {
   color: string = 'black';

   switchColor() {
       this.color =
           this.color === 'black' ? 'white' : 'black';
   }
}

class Circle extends Shape {
   switchColor() {
       super.switchColor();
       console.log(`Color is ${this.color}.`);
   }
}

const circle = new Circle();

circle.switchColor();
circle.switchColor();

输出结果:

TypeScript类继承

  • 第11行,Circle类重写了Shape类的switchColor方法。
  • 第12行,使用了super关键字来调用Shape类中的switchColor方法。

若派生类重写了基类中的受保护成员,则可以将该成员的可访问性设置为受保护的或公有的。也就是说,在派生类中只允许放宽基类成员的可访问性。例如,下例中Base类的三个成员变量都是受保护成员。在派生类Derived中不允许将其重写为私有成员。示例如下:

class Base {
   protected x: string = '';
   protected y: string = '';
   protected z: string = '';
}

class Derived extends Base {
   // 正确
   public x: string = '';

   // 正确
   protected y: string = '';

   // 错误!派生类不能够将基类的受保护成员重写为更严格的可访问性
   private z: string = '';
}

由于派生类是基类的子类型,因此在重写基类的成员时需要保证子类型兼容性。示例如下:

class Shape {
   color: string = 'black';

   switchColor() {
       this.color =
           this.color === 'black' ? 'white' : 'black';
   }
}

class Circle extends Shape {
   // 编译错误
   // 类型'(color: string) => void'不能赋值给类型'() => void'
   switchColor(color: string) {}
}

派生类实例化

在派生类的构造函数中必须调用基类的构造函数,否则将不能正确地实例化派生类。在派生类的构造函数中使用“super()”语句就能够调用基类的构造函数。示例如下:

class Shape {
   color: string = 'black';

   constructor() {
       this.color = 'black';
   }

   switchColor() {
       this.color =
           this.color === 'black' ? 'white' : 'black';
   }
}

class Circle extends Shape {
   radius: number;

   constructor() {
       super();

       this.radius = 1;
   }
}

此例第18行,在Circle类的构造函数中调用了基类Shape的构造函数。这样能够保证正确地实例化基类Shape中的成员。若派生类中定义了构造函数,但没有添加“super()”语句,那么将产生编译错误。

在派生类的构造函数中,引用了this的语句必须放在“super()”调用的语句之后,否则将产生编译错误,因为在基类初始化之前访问类的成员可能会产生错误。示例如下:

class Shape {
   color: string = 'black';

   constructor() {
       this.color = 'black';
   }

   switchColor() {
       this.color =
           this.color === 'black' ? 'white' : 'black';
   }
}

class Circle extends Shape {
   radius: number;

   constructor() {
       this.radius = 1;
       //  ~~~~
       //  编译错误,必须先调用 'super' 再访问 'this'
       super();
       // 正确
       this.radius = 1;
   }
}

在实例化派生类时的初始化顺序如下:

  • 初始化基类的属性。
  • 调用基类的构造函数。
  • 初始化派生类的属性。
  • 调用派生类的构造函数。

例如,下例中的数字标识与上面的步骤序号是对应的:

class Shape {
   color: string = 'black';   // 1

   constructor() {            // 2
       console.log(this.color);
       this.color = 'white';
       console.log(this.color);
   }
}

class Circle extends Shape {
   radius: number = 1;        // 3

   constructor() {            // 4
       super();

       console.log(this.radius);
       this.radius = 2;
       console.log(this.radius);
   }
}

const circle = new Circle();

输出结果:

TypeScript类继承

单继承

TypeScript中的类仅支持单继承,不支持多继承。也就是说,在extends语句中只能指定一个基类。示例如下:

class A {}
class B {}

class C extends A, B {}
//      ~
//      编译错误:类只能继承一个类

接口继承类

TypeScript允许接口继承类。若接口继承了一个类,那么该接口会继承基类中所有成员的类型。例如,下例中接口B继承了类A。因此,接口B中包含了string类型的成员x和方法类型y。示例如下:

class A {
   x: string = '';

   y(): boolean {
       return true;
   }
}

interface B extends A {}

declare const b: B;

b.x;   // 类型为string
b.y(); // 类型为boolean

在接口继承类时,接口不但会继承基类的公有成员类型,还会继承基类的受保护成员类型和私有成员类型。如果接口从基类继承了非公有成员,那么该接口只能由基类或基类的子类来实现。示例如下:

// 正确,A 可以实现接口 I,因为私有属性和受保护属性源自同一个类 A
class A implements I {
    private x: string = '';
    protected y: string = '';
}

// 接口 I 能够继承 A 的私有属性和受保护属性
interface I extends A {}

// 正确,B 可以实现接口 I,因为私有属性和受保护属性源自同一个类 A
class B extends A implements I {}

// 错误!C 不是 A 的子类,无法实现 A 的有属性和受保护属性
class C implements I {}

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!