类在面向对象编程中起着创建对象的蓝图,描述所创建的对象共同的属性和方法的作用。
创建类
与JS差不多,通过 Class 关键字来定义一个类:
class Greeter {// 静态属性static cname: string = "Greeter";// 成员属性greeting: string;// 构造函数 - 执行初始化操作constructor(message: string) {this.greeting = message;}// 静态方法static getClassName() {return "Class name is Greeter";}// 成员方法greet() {return "Hello, " + this.greeting;}
}let greeter = new Greeter("world");
接下来逐步讲解:
static cname:如果属性用static修饰的话(默认为public),表示静态属性不能通过实例对象访问到该属性,只能在类内调用或者通过类去访问,比如 Person.gender。
static getClassName:通过static修饰该方法(默认为public),表示静态方法 ,不能通过实例对象访问到该方法需要在类内调用或通过类去访问 ,比如 Person.sayHello()
构造函数:类的构造函数,对象创建时调用 限制。每个类仅能一个构造函数
this的指向:
1、在实例方法中,this就表示当前的实例
2、在构造函数中的当前对象就是新建的那个对象
3、可以通过this向新建的对象中添加属性
看看JS是怎么实现的:
var Greeter = /** @class */ (function () {// 构造函数 - 执行初始化操作function Greeter(message) {this.greeting = message;}// 静态方法Greeter.getClassName = function () {return "Class name is Greeter";};// 成员方法Greeter.prototype.greet = function () {return "Hello, " + this.greeting;};// 静态属性Greeter.cname = "Greeter";return Greeter;
}());
var greeter = new Greeter("world");
可以看到常态方法greet生成了对象原型链上的方法,静态方法static生成了对象上的方法。而无论是静态属性或者常态属性都是挂靠在对象上面。匿名函数返回了Greeter方法并立即执行。
class Greeter {// 静态属性static cname: string = 'dd'// 成员属性greeting: string = 'ff';}
var Greeter = /** @class */ (function () {function Greeter() {// 成员属性this.greeting = 'ff';}// 静态属性Greeter.cname = 'dd';return Greeter;
}());
没有默认值的话连属性都不会生成
class Greeter {// 静态属性static cname: string // 成员属性greeting: string ;}
var Greeter = /** @class */ (function () {function Greeter() {}return Greeter;
}());
如果我们用ES6的Class类来实现TS的Greeter类。则:
class Greeter {// 静态属性cname= "Greeter";// 成员属性greeting;// 构造函数 - 执行初始化操作constructor(message) {this.greeting = message;}// 静态方法static getClassName() {return "Class name is Greeter";}// 成员方法greet() {return "Hello, " + this.greeting;}}
注意ES6没有静态属性static,只有静态方法。
继承类
class Animal{name:stringage:number//构造函数constructor(name:string,age:number){this.name = namethis.age = age}// 自定义方法sayHello(){console.log('动物在叫~')}} class Dog extends Animal{// 重写父类方法sayHello(): void {console.log('汪汪汪')}}class Cat extends Animal{//重写父类方法sayHello(): void {console.log('喵喵喵')}}const dog = new Dog('旺财',4) const cat = new Cat('咪咪',3)console.log(dog) //Dog {name: '旺财', age: 4}console.log(dog.sayHello()) //汪汪汪console.log(cat) //Cat {name: '咪咪', age: 3}console.log(cat.sayHello()) //喵喵喵
直接看代码:
Dog extends Animal
- 此时,Animal被称为父类,Dog被称为子类
- 使用继承后,子类将会拥有父类所有的方法和属性
- 通过继承可以将多个类中共有的代码写在一个父类中,
- 这样只需要写一次即可让所有的子类都同时拥有父类中的属性和方法
- 如果希望在子类中添加一些父类中没有的属性或方法直接加就行
- 这种子类覆盖掉父类方法的形式,我们称为方法重写
继承类的构造函数constructor可以不用写。默认会沿用父类的构造函数。
继承也可以用于interface接口身上。
interface a {name:string
}
interface b extends a {age:number
}
let obj :b = {name:'ss',age:3}
super方法
class Animal{name:stringage:numberconstructor(name:string,age:number){this.name = namethis.age = age }sayHello(){console.log('动物在叫~')}
}
class Dog extends Animal{//子类构造函数constructor(name:string,age:number){super(name,age)}//重写父类方法sayHello(): void {super.sayHello()console.log('汪汪汪')}
}const dog = new Dog('旺财',2)
console.log(dog) //Dog {name: '旺财', age: 2}
console.log(dog.sayHello()) //动物在叫~ 汪汪汪
super: 调用父类构造函数
场景:如果在子类中写了构造函数,在子类构造函数中"必须"对父类的构造函数进行调用
在类的方法中 super就表示当前类的父类 super.sayHello();
抽象类abstract
- 以abstract开头的类是抽象类
- 抽象类和其他类区别不大,只是不能用来创建对象
- 抽象类就是专门用来被继承的类
- 抽象类中可以添加抽象方法
abstract class Animal{name: string;constructor(name: string) {this.name = name;}abstract sayHello():void //只能这样写 不能有具体实现什么 否则报错
}
注意抽象类的方法是不能有函数体的。抽象方法只能定义在抽象类中,子类必须对抽象方法进行重写。
class Dog extends Animal{//注意:必须要重写父类,抽象的方法,不然会报错sayHello():void {console.log('汪汪汪')}}
private、public、protected和访问器
TS可以在属性前添加属性的修饰符
class Person{private _name:stringprivate _age:numberconstructor(name:string,age:number){this._name = namethis._age = age}setName(value){this._name = value}getName(){return this._name}
}
上文提到加了static静态修饰的属性虽然不能通过实例去获取,但是它可以通过类的属性去获取(比如Person.name)。如果我们希望其无法通过任何渠道去获取。那就可以使用private修饰。
如果你想要让私有属性能够在外部被访问被修改。就像上文一样,在类内部添加方法暴露私有属性。
- public 修饰的属性可以在任意位置访问(修改)默认值
- private 私有属性,私有属性只能在类内部进行访问(修改)
- 通过在类中添加方法使得私有属性可以被外部访问
- protected 受保护的属性,只能在当前类和当前类的子类中访问(修改)
而实际上,TS已经贴心的为我们提供了访问器队私密属性进行操作。不需要我们在类中添加方法。
TS中设置getter方法的方式: 此时再使用personA.name时,实际上是调用了get name()方法。
class Person{private _name:string...get name() {return this._name;}
}
调用:
let personA = new Person('dd',12);
console.log(personA.name)
TS中设置setter方法的方式:此时再使用personA.name = xxx时,实际上是调用了set name()方法!
set name(value) {this._name = value;
}
调用:
personA.name ='xxxxx'
protected修饰:
但是如果是这样就可以:
const b = new B(123);
console.log(b.test())
interface接口
接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法
同时接口也可以当成类型声明去使用
可以定义相同的接口,最后会合并在一起
接口里面的属性和方法不能有初始值
接口只考虑对象结构,而不考虑实际值
在接口中定义方法都是抽象方法,也就意味着需要重写
interface myinterface {name:string,age:number}
interface myinterface{gender:stringsayHello():void
}
let aaaa:myinterface ={name:'a',age:33,gender:'xx',sayHello:()=>{console.log(123)}}
而定义类时,可以使类去实现一个接口。实现接口就是使类满足接口的要求
class myClass implements myinterface{constructor(public name:string, public age:number,public gender:string){this.name = namethis.age = agethis.gender = gender}sayHello() {console.log('你好呀~')}
}
泛型与类
使用场景:在定义函数或类时,如果遇到类型不明确就可以使用泛型
具体请看我的另外一篇推文。TS与泛型