前言
TypeScript 由 Microsoft 开发,是一种开源的、面向对象的编程语言。当然,它是 JavaScript 的一个强大的语法超集,并改进了该语言的可选静态类型。其中最重要的一点就是TypeScript 支持面向对象的所有特性。
在JavaSript中,可以说一切皆为对象,当我们用面向对象的思想进行封装代码时,不论是用构造函数,对象,还是ES6新增的class时,其实本质上都是在使用JavaScript的Object对象,这就要求在进行一些比较复杂的对象封装时,需比较熟悉对象的原型链等原理,才能封装出简洁,易读,易扩展的对象。面向对象的三大特征为继承、封装、多态。JavaScript虽然可以模拟实现继承和封装,但无法良好的模拟实现多态。
ES5实现类代码示例:
// 定义一个Person类
function Person(name, age) {
this.name = name; // 公有属性
var privateAge = age; // 私有属性(通过闭包保存)
// 获取年龄的公共方法
this.getAge = function() {
return privateAge;
};
}
// 创建一个新的Person对象
var person1 = new Person("张三", 20);
console.log(person1.name); // 输出 "张三"
console.log(person1.age); // undefined (无法直接访问私有属性)
console.log(person1.getAge()); // 输出 20 (调用公共方法获取私有属性值)
ES6实现类代码示例:
class Person {
// 构造函数
constructor(name, age) {
this.name = name
this.age = age
}
// 方法
say(){
console.log('我能说话')
}
}
// 实例化
let zs = new Person('张三', 24)
// 实例化
let ls = new Person('李四', 24)
console.log(zs)
console.log(ls)
TS实现类代码示例:
class Person{
public name:string;
private age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
public setAge(age:number){
this.age = age;
}
public getAge():number{
return this.age;
}
}
let person = new Person("xiaoming",20);
console.log(person.name);
console.log(person.getAge());
ES6引入了类的概念,为 JavaScript 增加了面向对象编程的能力。ES6 中的类是一种语法糖,本质上仍然是基于原型的继承。使用类可以定义构造函数、实例方法和静态方法,并且支持继承和类之间的关系。TypeScript新增了类、接口、泛形等等这些特性。相比“灵活自由”的JavaScript式封装,运用这些特性将会使初学者写的代码更加规范,更易于阅读和扩展,降低了一些封装的门槛,同时也方便了一些熟悉面向对象编程的开发者进行开发,TypeScript相比ES6具有更强的类型系统和其他功能 ,并且而对于大型的项目开发,这种面向对象的开发方式能有助于团队合作,提高开发人员的工作效率。
类的特性
传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了class。TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。这一节主要介绍TypeScript中类的用法。
虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,对于学过Java等面向对象的小伙伴来说,学习TypeScript是非常轻松的,接下来,我就从一下常见属性来讲解TypeScript面向对象特性。
类(Class):定义了一件事物的抽象特点,包含它的属性和方法
对象(Object):类的实例,通过 new 关键字生成
面向对象(OOP)的三大特性:封装、继承、多态
封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat
和 Dog
都继承自 Animal
,但是分别实现了自己的 eat
方法。此时针对某一个实例,我们无需了解它是 Cat
还是 Dog
,就可以直接调用 eat
方法,程序会自动判断出来应该如何执行 eat
存取器(getter & setter):用以改变属性的读取和赋值行为
修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如public表示公有属性或方法
抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
1. TypeScript中的类和类的实例化
定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
字段 : 字段是类里面声明的变量。字段表示对象的有关数据。
构造函数 :类实例化时调用,可以为类的对象分配内存。
方法 : 方法为对象要执行的操作。
class {
[修饰符] 字段名:[字段类型];
constructor([参数1:类型,....]){
// 执行一些初始化操作
},
[修饰符] 方法名([参数1:类型,....):返回值类型{
// 方法体....
}
}
例如:
class Person{
public name:string;
private age:number;
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
public setAge(age:number){
this.age = age;
}
public getAge():number{
return this.age;
}
}
let person = new Person("xiaoming",20);
console.log(person.name);
console.log(person.getAge());
2. 类的继承,以及类继承后super的使用
class User{
private name:string;
constructor(name:string){
this.name = name;
}
public setName(name:string):void{
this.name = name;
}
public getName():string{
return this.name;
}
}
class Admin extends User{
private password:string;
constructor(name:string,password:string){
super(name);
this.password = password;
}
public setPassword(password:string){
this.password = password;
}
public getPassword():string{
return this.password;
}
}
let user1 = new User("xujingliang");
console.log(user1.getName());
let admin = new Admin("xujingliang","123456")
console.log(admin.getName());
console.log(admin.getPassword());
3. 类继承后的方法重写
类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写。
重写是子类继承父类之后,方法名称,参数名称,返回值类型全部相同只能修改方法体中的相关操作。
重写的优点:在拥有父类方法特征的同时,自身也可以有一些特有的特征
class User{
private name:string;
constructor(name:string){
this.name = name;
}
public setName(name:string):void{
this.name = name;
}
public getName():string{
return this.name;
}
public say():string{
return "My name is "+this.name;
}
}
class Admin extends User{
private password:string;
constructor(name:string,password:string){
super(name);
this.password = password;
}
public setPassword(password:string){
this.password = password;
}
public getPassword():string{
return this.password;
}
public say():string{
return "My name is "+super.getName()+" and my password is "+this.password;
}
}
let user = new User("xujingliang");
console.log(user.say());
let admin = new Admin("xujingliang","123456")
console.log(admin.say());
上面的代码示例中,我们先定义了一个User用户类,又定义了一个Admin管理员类。Admin类继承User类,并对User类的say()方法进行重写。
4. static关键字的使用
使用static生命的类字段或者方法不需要实例化对象,可以直接通过类名点字段名称或者类名点方法名称直接调用。
class Person{
public name:string;
private age:number;
static mark:string = "普通用户"
constructor(name:string,age:number){
this.name = name;
this.age = age;
}
public setAge(age:number){
this.age = age;
}
public getAge():number{
return this.age;
}
}
console.log(Person.mark);
示例代码中,声明了一个static字段mark,直接使用Person.mark就可以访问。
5. instanceof 运算符
instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。
class User{
private name:string;
constructor(name:string){
this.name = name;
}
public setName(name:string):void{
this.name = name;
}
public getName():string{
return this.name;
}
}
class Admin extends User{
private password:string;
constructor(name:string,password:string){
super(name);
this.password = password;
}
public setPassword(password:string){
this.password = password;
}
public getPassword():string{
return this.password;
}
}
let user1 = new User("xujingliang");
let admin = new Admin("xujingliang","123456")
console.log(user1 instanceof User);
console.log(user1 instanceof Admin);
console.log(admin instanceof Admin);
console.log(admin instanceof User);
注意:示例代码中,Admin类继承User类,因此用Admin示例化的对象既属于User类有属于Admin类
6. 访问控制修饰符
类成员可访问性定义了类的成员允许在何处被访问。和Java一样TypeScript也为类成员提供了三种可访问性修饰符。
1. public:类的公有成员没有访问限制,可以在当前类的内部、外部以及派生类的内部访问。类的公有成员使用public修饰符标识。在默认情况下,类的所有成员都是公有成员。
2. protected:类的受保护成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。类的受保护成员使用protected修饰符标识。
3. private:类的私有成员只允许在当前类的内部被访问,在当前类的外部以及派生类的内部都不允许访问。类的私有成员使用private修饰符标识。
4. readonly:使用 readonly 关键词设置成员只读,初始化过后,readonly 不管在外部还是内部都不允许再修改。
class User{
private username:string;
private password:string;
private readonly state:boolean = false;
constructor(username:string,password:string){
this.username = username;
this.password = password;
}
public setUsername(username:string):void{
this.username = username;
}
public getUsername():string{
return this.username;
}
public setPassword(password:string):void{
this.password = password;
}
public getPassword():string{
return this.password;
}
public setState(state:boolean):void{
// 此处会报错:Cannot assign to 'state' because it is a read-only property.(2540)
this.state = state;
}
}
7. 类的封装
面向对象的三大特性:封装、继承、多态。封装是指将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来对隐藏的信息进行操作和访问。我们在程序设计的过程中要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己来完成,不允许外部干涉,低耦合:就是, 仅暴露少量的方法给外部使用
结合访问修饰符private即可实现类的封装
class User{
private username:string;
private password:string;
constructor(username:string,password:string){
this.username = username;
this.password = password;
}
public setUsername(username:string):void{
this.username = username;
}
public getUsername():string{
return this.username;
}
public setPassword(password:string):void{
this.password = password;
}
public getPassword():string{
return this.password;
}
}
上述代码示例中,用户只能通过暴露的setUsername、getUsername、setPassword、getPassword等方法来访问和操作User的username和password属性。
8. 类和接口
类与类之间的一些共同点可以用接口去抽象,比如 人 和 动物 都有相同的特点,吃和行走,我们就可以通过接口去约束这两个类的公共能力。接口使用关键字 interface 声明,类可以使用关键字 implements 实现接口。
interface Person{
name:string
}
class User implements Person{
name:string;
constructor(name:string){
this.name = name;
}
public setName(name:string):void{
this.name = name;
}
public getName():string{
return this.name;
}
}
class Admin extends User{
private password:string;
constructor(name:string,password:string){
super(name);
this.password = password;
}
public setPassword(password:string){
this.password = password;
}
public getPassword():string{
return this.password;
}
}
let user1 = new User("xujingliang");
let admin = new Admin("xujingliang","123456")
9. 抽象类和抽象方法
抽象类在某种程度上跟接口有点类似,它也是用来约束子类当中必须要拥有某些成员,但是不同于接口的是,抽象类可以包含一些具体的实现,而接口只能够是一些成员的抽象,不包含具体的实现,一些比较大的类目建议使用抽象类,比如动物类,因为我们所说的动物它只是一个泛指,并不够具体,那在它的下面一定有更细化的分类,例如小狗小猫之类。
定义抽象类的方式:通过关键词 abstract
1. 类型被定义为抽象类过后,它只能够被继承,不能够再使用 new 的方式去创建对应的实例对象,在这种情况下我们就必须要使用 子类 去继承这个类型。
2. 在抽象类当中我们还可以去定义一些抽象方法,需要注意的是抽象方法也不需要方法体。
3. 当我们的父类有抽象方法时,我们的子类就必须要实现这个方法。
// 抽象类
abstract class Animal{
// 非抽象方法
cry(message:string):void{
console.log(message)
}
// 抽象方法 抽象方法不能有具体的实现
abstract say():void
}
class Dog extends Animal{
// 如果继承的抽象类中有抽象方法,则子类必须去实现抽象方法,否则编译检查报错
public say():void{
console.log("I am Dog");
}
}