类
- 类(Class):定义了一件事物的抽象特点,它将数据(属性)以及这些数据上的操作(方法)封装在一起。
- 对象(Object):类的实例,通过 new classname生成
- 类是抽象的,不占用内存,而对象是具体的,占用存储空间。
JavaScript 是基于对象的语言,而TypeScript是面向对象的语言。
- 面向对象的三大特性:封装、继承、多态
- 封装:将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承:子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态:由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自 Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat 方法,程序会自动判断出来应该如何执行 eat
- 存取器(getter & setter):用以改变属性的读取和赋值行为
- 修饰符:修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
- 抽象类:抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口:不同类之间共有的属性或方法,可以抽象成一个接口。接口可以被类实现。一个类只能继承自另一个类,但是可以实现多个接口
类的定义
使用 class 定义类,类中声明类的成员(都是可选的)
- 字段也就是属性,类中可包含0到多个
- 构造函数 用constructor定义,类中可包含0到1个
- 方法也就是行为动作,类中可包含0到多个
class Class_name {
// 类体
//构造方法
constructor(){}
}
通过 new 类名生成新实例的时候,会自动调用类中构造方法constructor,可以给生成的实例对象添加属性。
类的属性和方法
直接定义的成员是实例成员,需要通过new创建的实例对象来访问实例成员。
class Car {
//类的实例属性,通过new创建的实例对象来访问属性
engine:string; //没有初始化
price:number = 15;//初始化值为15
name:string;
age:number;
constructor(name:string,age:number){
//类中需要先定义好属性,才可以在构造方法中给实例对象添加属性
this.name = name;
this.age = age;
}
//类的实例方法
disp():void {
console.log(this.engine, this.price);
}
toGo(len:number):boolean {
console.log(`走了${len}米远`);
return true;
}
//字段式的方法,本质上这不是方法,而是属性
m1 = (a:number, b:number):void=>{
console.log("m1()")
}
m2:(a:number,b:number)=>void = (a, b)=>{
console.log("m1()",a+b)
}
m3 = function():void {
console.log("m3()")
}
}
let car = new Car();
console.log(car.engine, car.price);
car.disp();
声明属性时加类型声明以及初始化属性值都是是可选的; 类的方法参数和返回值都可声明类型,但都是可选的。
方法中可以使用this,该this指向调用方法的对象, 所以方法里可以使用 this.方法和this.属性访问实例成员。
静态方法和静态属性
使用 static 修饰符修饰的方法和属性称为静态成员,它们不需要实例化,是类自己的属性和方法,实例对象是访问不到静态成员的。静态方法是直接通过类来调用,不能使用实例对象来访问静态成员。
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
static num = 42;
constructor() {
// ...
}
}
let a = new Animal();
Animal.isAnimal(a); // true
a.isAnimal(a); // TypeError: a.isAnimal is not a function
console.log(Animal.num); // 42
构造函数和this
使用 constructor 定义构造函数,通过 new 生成新实例的时候,会自动调用构造函数。
构造函数constructor中this表示当前使用new创建的实例
- 主要用于初始化类的属性
- 类的对象创建时自动调用执行
- 没有返回值
实例方法中的this表示当前调用方法的对象
class Car {
engine:string;
price:numbe;
constructor(engine:string, price:number) {
this.engine = engine;
this.price = price;
}
printf():void {
console.log(`engin=${this.engine},
price=${this.price}`);
}
}
总结:
- 构造函数可带0到多个参数 (构造函数没有返值,也不用声明返回值类型)
- 实例化对象
- 构造函数无参时,new 类名();
- 构造函数有参时,new 类名(为构造函数传实参)
- 实例化过程(new过程)
- 在堆中实例化对象,包括创建字段成员
- 执行构造函数
- 返回对象引用
- 构造函数存在的价值
- 初始化一些操作并为创建的对象属性赋值
- 构造函数中this值为新实例化的对象引用,常通过"this.字段=值"为创建对象的字段赋值;
- 方法中可以使用this,该this引用调用方法的对象, 所以方法里可以使用
this.方法和this.字段访问实例成员;
类的继承
使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。类的继承扩展现有的类
-
子类继承父类后,子类的实例就拥有了父类中的所有属性和方法,继承的作用是增强代码的可复用性
-
将子类共用的方法抽象出来放在父类中,自己特殊逻辑放在子类中重写父类的逻辑
-
子类调用父类的方法,通过super
//父类
class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHellow(){
console.log('动物在叫~');
}
}
//子类
class Dog extends Animal{
//子类不需要再定义属性,会继承父类中的所有属性和方法
constructor(name:string,age:number) {
super(name,age); // 调用父类的构造方法constructor
}
//可以调用父类的方法,还可以重写父类的方法
sayHellow(){
console.log('Dog类的sayHellow方法');
//调用父类的方法
super.sayHellow()
}
}
//子类
class Cat extends Animal{
}
let dog=new Dog('youngmini',4);
let cat=new Cat('maomao',6);
console.log(dog);
dog.sayHellow();
console.log(cat);
cat.sayHellow();
class Animal {
public name;
constructor(name) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
//类的继承
使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
class Cat extends Animal {
constructor(name) {
super(name); // 调用父类的 constructor(name)
console.log(this.name);
}
sayHi() {
return 'Meow, ' + super.sayHi(); // 调用父类的 sayHi()
}
}
let c = new Cat('Tom'); // Tom
console.log(c.sayHi()); // Meow, My name is Tom
只能单继承,一个类的父类只能有一个,但是一个类可以有多个子类。子类也可以再次被当作父类,形成树结构。
子类添加父类没有的成员和重写父类方法
class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHellow(){
console.log('动物在叫~');
}
}
class Dog extends Animal{
run(){
console.log(`${this.name}在跑`)
}
}
class Cat extends Animal{
}
let dog=new Dog('youngmini',4);
let cat=new Cat('maomao',6);
console.log(dog);
dog.sayHellow();
console.log(cat);
cat.sayHellow();
dog.run();
如果在子类中添加了父类相同的方法,则子类中子类的方法会覆盖继承的父类方法,这就是子类重写继承的父类方法。
class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHellow(){
console.log('动物在叫~');
}
}
class Dog extends Animal{
run(){
console.log(`${this.name}在跑`)
};
sayHellow(): void {
console.log('汪汪~~');
}
}
class Cat extends Animal{
sayHellow(): void {
console.log('喵喵~~')
}
}
let dog=new Dog('youngmini',4);
let cat=new Cat('maomao',6);
console.log(dog);
dog.sayHellow();
console.log(cat);
cat.sayHellow();
dog.run();
super关键字
class Animal {
name:string;
age:number;
constructor(name:string,age:number){
this.name=name;
this.age=age;
}
sayHellow(){
console.log('动物在叫~');
}
}
class Dog extends Animal{
kind:string;
constructor(name:string,age:number,kind:string){
// 调用父类的构造函数
super(name,age);
this.kind=kind;
}
run(){
console.log(`${this.name}在跑`)
};
sayHellow(): void {
console.log('汪汪~~');
}
}
let dog=new Dog('youngmini',4,'犬科');
console.log(dog);
dog.sayHellow();
dog.run();
- super 可以调用父类上的方法和属性(相当于ES5的语法,在静态方法和构造函数中指向父类; 在普通函数中指向父类的prototype;)
- super(<实参>)要位于构造方法中的this使用之前
- 子类对象的创建过程:创建父类对象,然后在父类对象基础上追加子类成员得到子类对象,即先声明子类的属性然后子类构造函数再执行,子类构造函数中super(<实参>)再调用父类构造函数的执行
- 子类继承了父类构造函数必须使用super(<实参>),不管父类是否写了构造方法。没有继承父类,构造函数中就不能有super(<实参>)
- 如果父类和子类都有构造函数,并且父类构造函数有参数,那么要求子类构造函数 super()中必须为父类传参数。
类的存取器
存取器可以控制对对象成员的访问。
class Name {
firstName:string;
lastName:string;
constructor(firstName:string,lastName:string){
this.firstName = firstName;
this.lastName = lastName;
}
// 读取器:用来读取数据
get fullName(){
//return this.firstName + this.lastName;
return 'jessica';
}
// 设置器:用来设置数据
set fullName(value){
console.log(value);
}
}
类的修饰符
TypeScript 可以使用三种访问修饰符,分别是 public、private 和 protected。
public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public。private修饰的属性或方法是私有的,不能在声明它的类的外部访问,子类可以继承但是子类不能访问。protected修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的。
class Animal {
public name:string;
private age:number;
public constructor(name:string,age:number) {
this.name = name;
this.age = age;
}
public ageFun() {
//类中可以访问
console.log(this.age);
}
}
let a = new Animal('jessica',18);
console.log(a.name); // jessica
a.name = 'Tom';
console.log(a.name); // Tom
let b = new Animal('hyomin',18);
//编译报错
console.log(b.age);
用 private 修饰的属性或方法,在子类中也是不允许访问的:
class Animal {
private name:string;
public constructor(name:string) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name:string) {
super(name);
// 编译报错
console.log(this.name);
}
}
用 protected 修饰,则允许在子类中访问:
class Animal {
protected name:string;
public constructor(name:string) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name:string) {
super(name);
console.log(this.name);
}
}
当构造函数修饰为 private 时,该类不允许被继承或者实例化:
class Animal {
private name:string;
public constructor(name:string) {
this.name = name;
}
}
// 编译报错
class Cat extends Animal {
constructor(name:string) {
super(name);
console.log(this.name);
}
}
// 编译报错
let a = new Animal('Jack');
构造函数修饰为 protected 时,该类只允许被继承,子类之外不能访问到:
class Animal {
public name:string;
protected constructor(name:string) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name:string) {
super(name);
}
}
// 编译报错
let a = new Animal('Jack');
参数属性和readonly
readonly只读属性关键字,但在构造方法中是可以修改的。
class X {
readonly age:number;
constructor(age:number){
this.age = age;
}
update(){
// 编译报错
this.age = 15;
}
}
const x = new X(18);
console.log(x)//X { age: 18 }
// 编译报错
x.age = 27;
readonly定义在参数上,会创建并且初始化的age参数。
class X {
readonly age:number;
constructor(readonly age:number){
this.age = age;
}
}
public、private、protected修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,在构造函数中不再需要进行赋值使代码更简洁。
class Animal {
// public name: string;
public constructor(public name:string) {
// this.name = name;
}
}
如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。
class Animal {
// public readonly name;
public constructor(public readonly name) {
// this.name = name;
}
}