Typescript 实战 --- (5)类
1、es类 vs ts类
相同点:类成员的属性都是实例属性,而不是原型属性;类成员的方法都是原型方法
不同点:ts 中类成员的属性必须有初始值,或者是在构造函数中被初始化
class Dog { constructor(name: string) { this.name = name; } name: string; run() {} } console.log(Dog.prototype); // Dog { run: [Function] } let dog = new Dog('Bob'); console.log(dog); // Dog { name: 'Bob' } // name 属性只在实例上,不在原型上;run 方法只在原型上,不在实例上
声明一个类的传统写法和简化写法
// 传统写法 class Person { public name: string; constructor(name: string) { this.name = name; } } // 简化写法 class Animal { constructor(public name: string) {} } class Dog extends Person{ constructor(public leg: number, public name: string) { super(name); } } const person = new Person('Bob'); const dog = new Dog(4, 'XiaoTianQuan');
2、类的继承
与es一样,ts中类的继承也是通过 extends 关键字,并且,在子类构造函数中,super关键字必须放在第一行
class Animal { constructor(name: string) { this.name = name; } name: string; } class Dog extends Animal { constructor(name: string, leg: number) { super(name); this.leg = leg; } leg: number; intro() { console.log(`My name is ${this.name}, I hava ${this.leg} legs.`); } } let bob: Dog = new Dog('Bob', 4); bob.intro(); // My name is Bob, I hava 4 legs.
类的成员方法可以直接返回一个 this,这样就可以实现链式调用
class WorkFlow { step1() { console.log('step1...') return this } step2() { console.log('step2...') return this } } new WorkFlow().step1().step2() // step1... // step2...
在继承的时候,this 也可以表现出多态,这里的多态指的是 this 既可以是父类型,也可以是子类型
class WorkFlow { step1() { console.log('step1...') return this } step2() { console.log('step2...') return this } } class MyWorkFlow extends WorkFlow { next() { console.log('next...') return this } } new MyWorkFlow().next() // this 指向子类 .step1() // this 指向父类 .next() // this 指向子类 .step2() // this 指向父类 // next... // step1... // next... // step2...
3、成员修饰符
3-1、public
对所有人可见,在 ts 中,类的所有成员都默认为 public
class Animal { constructor(name: string) { this.name = name; } // 默认是public,可省略 public name: string; public say () { console.log('hello... ...') } } class Dog extends Animal { constructor(name: string) { super(name); } } let bob: Dog = new Dog('Bob'); bob.say(); // hello... ...
3-2、private
只能被类本身调用,不能被类的实例或者子类调用
class Animal { constructor(name: string) { this.name = name; } name: string; private say () { console.log('hello... ...') } } class Dog extends Animal { constructor(name: string) { super(name); } } // private 不能用于实例 let jorge: Animal = new Animal('Jorge'); jorge.say(); // Property 'say' is private and only accessible within class 'Animal'. // private 不能用于子类 let bob: Dog = new Dog('Bob'); bob.say(); // Property 'say' is private and only accessible within class 'Animal'.
用于构造函数,则表示这个类既不能实例化,也不能被继承
class Animal { private constructor(name: string) { this.name = name; } name: string; } // Cannot extend a class 'Animal'. Class constructor is marked as private. class Dog extends Animal { constructor(name: string) { super(name); } } let a: Animal = new Animal('aaa'); // Constructor of class 'Animal' is private and only accessible within the class declaration
3-3、protected
只能被类本身或者子类调用,不能被实例调用
class Animal { constructor(name: string) { this.name = name; } name: string; protected say() { console.log(`hello ${this.name}`) } } class Dog extends Animal { constructor(name: string) { super(name); this.say(); } } let d: Dog = new Dog('ddd'); // hello ddd let a: Animal = new Animal('aaa'); a.say(); // Property 'say' is protected and only accessible within class 'Animal' and its subclasses.
用于构造函数,表示这个类不能被实例化,只能被继承,相当于是声明了一个基类
class Animal { protected constructor(name: string) { this.name = name; } name: string; } class Dog extends Animal { constructor(name: string) { super(name); } } let d: Dog = new Dog('ddd'); let a: Animal = new Animal('aaa'); // Constructor of class 'Animal' is protected and only accessible within the class declaration.
3-4、readonly
只读属性必须在声明时或构造函数里被初始化,且不可更改
class Dog { constructor(name: string) { this.name = name; } readonly name: string; readonly leg: number = 4; } let d: Dog = new Dog('Bob'); d.name = 'Carl'; // Cannot assign to 'name' because it is a read-only property. d.leg = 2; // Cannot assign to 'leg' because it is a read-only property.
3-5、static
静态属性,只能通过类名来调用。调用方式是 类名.静态属性名,类的静态成员也可以被继承
class People { constructor(name: string) { this.name = name; } name: string; static legs: number = 2; } class Student extends People { constructor(name: string) { super(name); } } console.log(People.legs); // 2 console.log(Student.legs); // 2
构造函数的参数也可以添加修饰符,作用是将参数自动变成实例的属性,这样就不用在类中去定义参数了
class People { constructor( public name: string) { this.name = name; } // name: string; // 标识符“name”重复 }
4、getter/setter 存取器
如果一个类没有使用存取器,那么,其成员属性是可以被随意修改的
class Dog { constructor(name: string) { this.name = name; } name: string; getName() { console.log("name: ", this.name); } } let d: Dog = new Dog('Bob'); d.name = 'Carl'; d.getName(); // name: Carl
而封装的基本原则是尽可能的隐藏内部实现细节,只保留一些对外接口使之与外部发生联系,这个时候就需要用到存取器了
class Dog { constructor() {} private _name: string; get name(): string { return this._name; } set name(name: string) { if(name.length > 10) { console.log('Error: the name is too long!') } else { this._name = name; } } } let d: Dog = new Dog(); d.name = 'hello world!'; // Error: the name is too long! console.log(d.name); // undefined d.name = 'Bob'; console.log(d.name); // Bob
5、抽象类
所谓抽象类,指的是只能被继承,不能被实例化的类
抽象类的好处是可以抽离出一些事物的共性,有利于代码的复用和扩展
使用 abstract 关键字定义抽象类和在抽象类内部定义的抽象方法
abstract class Animal {} let a: Animal = new Animal(); // 无法创建抽象类的实例
// 抽象类可以被继承 abstract class Animal { eat() { console.log('eat something... ...') } } class Dog extends Animal { name: string; constructor(name: string) { super(); this.name = name; } } let dog: Dog = new Dog('Bob'); dog.eat(); // eat something... ...
抽象类中的抽象方法,不包含具体实现并且必须在子类中实现
abstract class Animal { abstract sleep(): void; } class Dog extends Animal { name: string; constructor(name: string) { super(); this.name = name; } sleep() { console.log('dog sleep... ...') } } let dog: Dog = new Dog('Bob'); dog.sleep(); // dog sleep... ...
abstract class Animal { abstract sleep(): void; } class Dog extends Animal { name: string; constructor(name: string) { super(); this.name = name; } } let dog: Dog = new Dog('Bob'); // Non-abstract class 'Dog' does not implement inherited abstract member 'sleep' from class 'Animal'.
抽象类也可以实现 多态,多态就是在父类中定义一个抽象方法,在多个子类中,可以对这个方法有不同的实现,程序运行时会根据不同的对象执行不同的操作,这样就实现了运行时的绑定
abstract class Animal { abstract sleep(): void; } class Dog extends Animal { sleep() { console.log('dog sleep'); } } class Cat extends Animal { sleep() { console.log('cat sleep'); } } let dog: Dog = new Dog(); let cat: Cat = new Cat(); let animals: Animal[] = [dog, cat]; animals.forEach(item => item.sleep()); // dog sleep // cat sleep
6、类与接口
6-1、类与接口之间的关系
(1)、类实现接口的时候,必须实现接口中声明的所有属性
(2)、接口只能约束类的公用成员
(3)、接口不能约束类的构造函数
// 示例一:类实现接口的时候,必须实现接口中声明的所有属性 interface Animal { name: string; eat(): void; } class Dog implements Animal { name: string; constructor(name: string) { this.name = name; } } let dog = new Dog('Bob'); // Class 'Dog' incorrectly implements interface 'Animal'. // Property 'eat' is missing in type 'Dog' but required in type 'Animal'. // 示例二:接口只能约束类的公用成员 interface Animal { name: string; eat(): void; } class Dog implements Animal { private name: string; // 约束为私有属性 constructor(name: string) { this.name = name; } eat() { console.log('dog like bones'); } } let dog = new Dog('Bob'); // Class 'Dog' incorrectly implements interface 'Animal'. // Property 'name' is private in type 'Dog' but not in type 'Animal'. // 示例三:接口不能约束类的构造函数 interface Animal { name: string; new(name: string): void; // 在接口中约束构造函数 eat(): void; } class Dog implements Animal { name: string; constructor(name: string) { this.name = name; } eat() { console.log('dog like bones'); } } let dog = new Dog('Bob'); // Class 'Dog' incorrectly implements interface 'Animal'. // Type 'Dog' provides no match for the signature 'new (name: string): void'.
6-2、接口的继承
接口可以像类一样相互继承,并且一个接口可以继承多个接口
接口的继承的好处是:可以抽离出可重用的接口,也可以将多个接口合并成一个接口
interface Human { name: string; } interface Man extends Human { run(): void; } interface Child { cry(): void; } interface Boy extends Man, Child {} // Error:必须实现所继承的所有父接口 let boy: Boy = {}; // Type '{}' is missing the following properties from type 'Boy': run, name, cry let boy: Boy = { name: 'Jorge', run() {}, cry() {} };
6-3、接口继承类
相当于接口把类的成员都抽象了出来,只有类的成员结构,而没有具体的实现、
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
接口在抽离类的成员时,不仅抽离了公共成员,也会抽离私有成员和受保护成员
class Point { private x: number; // 私有成员 public y: number; } interface Point3d extends Point { z: number; } // Error:属性x是私有成员,是不能被子类或实例调用的。 // 这个报错也恰好说明了,接口抽离类时,抽离了私有成员,导致了下面的报错 let point3d: Point3d = {x: 1, y: 2, z: 3}; // 不能将类型“{ x: number; y: number; z: number; }”分配给类型“Point3d”。 // 属性“x”在类型“Point3d”中是私有属性,但在类型“{ x: number; y: number; z: number; }”中不是
接口与类之间的关系:
(1)、接口之间是可以相互继承的,这样可以实现接口之间的复用
(2)、类之间也是可以相互继承的,可以实现类中属性和方法的复用
(3)、类可以实现接口,但是接口只能约束类的公用成员
(4)、接口可以继承类,相当于接口抽离了类的所有成员包括 公用、私有和受保护成员