传统的JavaScript程序使用函数和基于原型的继承来创建可重用的组件,但是我们更习惯于使用面向对象的方式来开发。ES6已经可以使用class关键字进行面向对象的方式开发了,使用TypeScript不管要编译到哪个ES规范版本的JavaScript,都可以使用class等一系列关键字来进行面向对象的方式开发,功能更强大。
类
// 类
class FirstClass {
msg: string;
//构造函数
constructor(msg: string) {
this.msg = msg;
}
Show() {
console.log('Hello ' + this.msg);
}
}
let first = new FirstClass('world');
first.Show();
// 输出
// Hello world
class 关键字定义一个类。
constructor 定义一个构造函数。一个构造函数就是你用 new 关键字创建一个类的实例时会调用的方法。
继承
面向对象的三个要素是封装、继承、多态。封装已经体现在类的定义中了,通过继承可以扩展一个类的功能,也可以体现多态这一特性。
class A {
a: string;
constructor(a: string) {
this.a = a;
}
show() {
console.log(this.a + ' from A')
}
}
class B extends A {
b: string;
constructor(a: string, b: string) {
super(a);//调用父类构造函数
this.b = b;
}
show() {
console.log(this.a + ' from B ');
}
}
let a: A = new A('a');
a.show();//a from A
let b: B = new B('a', 'b');
b.show();//a from B
let c: A = new B('c', 'b');
c.show();//c from B
//console.log(c.b);//错误,无法访问派生类的属性
- 继承使用关键字extends。B 继承 A ,则称A是父类,B 是子类。
- 父类中有构造函数,则子类的构造函数必须调用父类的构造函数。使用关键字 super 调用父类的构造函数,这个函数在子类构造函数中必须放到最开始的地方。
- 用父类的类型实例化一个子类会导致访问不到子类的属性。let c: A = new B('c', 'b'); 这里对象 c 没法访问子类中定义的属性 b。
- 面向对象中的 继承 强调的是子类与父类的相似性(从父类继承过来的属性与方法)以及扩展父类。而 多态 强调的是子类的不同形态,即跟父类不同的部分。
公共,私有与受保护的修饰符
public 表示公共的,外部可以访问的。如果不写修饰符,则默认是public。
private 表示私有的,只有内部可以访问的。
protected 表示受保护的,只有类与其子类中可以访问。
class Animal {
name: string
protected readonly weight: number;
private cry: boolean;
protected constructor(name: string, weight: number) {
this.name = name;
this.weight = weight;
this.cry = true;
}
show() {
console.log(`${this.name} ${this.weight},
can cry ${this.cry}`);
}
}
class Dog extends Animal {
constructor(name: string, weight: number) {
super(name, weight);
}
run() {
//cry是基类的private属性,子类无法访问
//console.log(`${this.name} ${this.weight},
// can cry ${this.cry}`);
console.log(`${this.name} ${this.weight},
I can run`);
}
}
class Bird1 extends Animal {
constructor(name: string, weight: number) {
super(name, weight);
}
fly() {
console.log(`${this.name} ${this.weight},
I can fly`);
}
}
let dog = new Dog('Dog', 123);
//console.log(dog.weight);//错误,外部无法访问protected定义的属性
dog.run();
//输出
/*
Dog 123,
I can run
*/
let bird = new Bird1('Bird1', 11.3);
bird.fly();
//输出
/*
Bird1 11.3,
I can fly
*/
dog.show();
bird.show();
//输出
/*
Dog 123,
can cry true
Bird1 11.3,
can cry true
*/
- 父类中 public 与 protected 的属性与方法在子类中都可以访问, private 定义的属性与方法无法访问(父类的 cry 属性子类无法访问)。在类的外部只能访问 public 定义的属性与方法(protected 属性无法访问)。
- 父类的构造函数定义成 protected ,则无法使用这个父类实例化一个对象。
class Bird2 {
name: string
protected readonly weight: number;
private cry: boolean;
constructor(name: string, weight: number) {
this.name = name;
this.weight = weight;
this.cry = true;
}
show() {
console.log(`${this.name} ${this.weight},
can cry ${this.cry}`);
}
fly() {
console.log(`${this.name} ${this.weight},
I can fly`);
}
}
let bird2 = new Bird2('Bird2', 13.1);
//bird = bird2;//错误,不是从同一个基类派生,类型不兼容
Bird2 与 Bird1 拥有相同的属性与方法,不同的是 Bird1 是通过继承获得了一部分,而Bird2 是全部自己定义的。这两个类是不兼容的。如果想要类型兼容,除了要有相同的属性定义,还必须都是来自同一处声明时,才认为这两个类型是兼容的,也就是继承与同一个父类或实现了同一个接口。
参数属性
class Demo {
constructor(private name: string) {
console.log(this.name);
}
}
let demo = new Demo('Demo');//Demo
代码中并没有定义一个 name 属性,但是在构造函数中却可以访问,这是因为在构造函数中使用了参数属性,private name: string 来创建和初始化 name 属性。
存取器
如果想访问类中 protected 或 private 定义的属性要怎么办?
class Demo1 {
private _name: string;
get name(): string {
return this._name;
}
set name(name: string) {
this._name = name;
}
}
let demo1 = new Demo1();
demo1.name = 'Demo1';
console.log(demo1.name);//Demo1
- 为了不破坏封装性,有些属性必须定义成 protected 或 private,但是在类的外部又需要调整这个属性,这就要用到get和 set来访问了。
- get提供给外部访问属性的权限。
- set提供给外部修改属性的权限。
静态属性
class Add {
static num: number = 19;
static show() {
console.log(Add.num);
}
show() {
console.log(Add.num);
}
}
console.log(Add.num);//19
Add.show();//19
let add1 = new Add();
let add2 = new Add();
Add.num++;
add1.show();//20
add2.show();//20
- 使用关键字 static 定义一个静态的属性或方法。静态属性是不属于任何对象的,必须使用类名加属性名的方式访问(如例子中:Add.show)。
- 一个类的所有实例共享这个类中定义的静态属性。(例子中给静态属性加1后,所有的实例访问这个属性发现结果是一致的)。
抽象类
抽象类是不能实例化的类。关键字 abstract 定义一个抽象类或抽象方法。
abstract class DemoA {
readonly name: string;
constructor(name: string) {
this.name = name;
}
abstract show(): void;
}
class DemoB extends DemoA {
show() {
console.log(this.name);
}
}
//错误,不能实例化一个抽象类
//let demoA = new DemoA()
let demoA: DemoA = new DemoB('DemoB');
let demoB: DemoB = new DemoB('DemoB');
demoA.show();//DemoB
demoB.show();//DemoB
- 抽象类不能用来实例化一个对象。
- 抽象方法是没有方法体的,子类如果不是抽象类,就必须实现父类中的所有抽象方法。
实现接口
TypeScript能够用 interface 来明确的强制一个类去实现某种契约规范。一个类通过关键字 implements 来实现一个接口。
enum Color {Red, Green, Blue};
interface Point {
readonly x: number;
readonly y: number;
}
interface Bullet {
name: string;//子弹名称
damage: number;//子弹伤害值
color: Color;//子弹颜色
speed: number;//子弹速度
bomb: boolean;//是否炸弹
aa?: string;
Shoot(from: Point, to: Point);
}
class Bullet1 implements Bullet {
constructor(public name: string,
public damage: number,
public color: Color,
public speed: number,
public bomb: boolean) {
}
Shoot(from: Point, to: Point) {
console.log(`bullet ${this.name},
damage ${this.damage},
color ${this.color},
is bomb ${this.bomb},
speed ${this.speed},
shooting from (${from.x}, ${from.y})
to (${to.x}, ${to.y})`);
}
}
let bullet = new Bullet1('Bullet1', 11, Color.Blue, 33, false);
bullet.Shoot({x: 10, y: 10}, {x: 100, y: 100});
//输出
/*
bullet Bullet1,
damage 11,
color 2,
is bomb false,
speed 33,
shooting from (10, 10)
to (100, 100)
*/
- 接口就像是一个规范一样,接口中定义的属性除了可选属性以外,实现这个接口的类必须全部拥有。例子中在构造方法里面使用 参数属性的方式定义的这些属性。
- 接口中定义的方法,类中必须实现。
幻想的路总是为有信念的人准备着。
网友评论