类的概述
类(class
)是面向对象编程语言中的一个重要概念。
面向对象编程(Object-Oriented Programming
,简称OOP
)是一种编程范式,其核心理念在于将程序中的数据与操作数据的方法有机地组织成对象,从而使程序结构更加模块化和易于理解。通过对象之间的协同合作,实现更为复杂的程序功能。
类(class
)是对象的蓝图或模板,它定义了对象的属性(数据
)和行为
(方法
)。通过类可以创建多个具有相似结构和行为的对象。例如定义一个 Person
类,其对象可以有张三
、李四
等等。
类的定义
定义类的语法如下图所示
代码:
class Person {
id: number;
name: string;
age: number = 18;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
introduce(): string {
return `hello,I am ${this.name},and I am ${this.age} years old`
}
}
对象创建
● 语法
创建对象的关键字为new
,具体语法如下
let person = new Person(1,'zhangsan');
●对象属性的访问
console.log(person.name); //读
person.name = 'lisi'; //写
●对象方法的调用
对象创建后,便可通过对象调用类中声明的方法,如下
let intro = person.introduce();
console.log(intro);
静态成员
Typescript
中的类中可以包含静态成员(静态属性
和静态方法
),静态成员隶属于类本身,而不属于某个对象实例
。静态成员通用用于定义一些常量
,或者工具方法
。
● 声明静态成员
定义静态成员需要使用static
关键字。
class Constants{
static count:number=1;
}
class Utils{
static toLowerCase(str:string){
return str.toLowerCase();
}
}
console.log(Constants.count);
console.log(Utils.toLowerCase('Hello World'));
● 使用静态成员
静态成员无需通过对象实例访问,直接通过类本身访问即可。
console.log(Constants.count);
console.log(Utils.toLowerCase('Hello World'));
继承
继承是面向对象编程中的重要机制,允许一个类(子类
或派生类
)继承另一个类(父类
或基类
)的属性
和方法
。子类可以直接使用父类的特性
,并根据需要添加新的特性或覆盖现有的特性
。这种机制赋予面向对象程序良好的扩展性。
下面通过一个例子演示继承的特性
class Student extends Person {
classNumber: string;
constructor(id: number, name: string, classNumber: string) {
super(id, name);
this.classNumber = classNumber;
}
introduce(): string {
return super.introduce()+`, and I am a student`;
}
}
let student = new Student(1,'xiaoming','三年二班');
console.log(student.introduce());
注意:
● 类的继承需要使用关键字extends
● 子类构造器中需使用super()
调用父类构造器对继承自父类的属性进行初始化。
● 在子类中可以使用this
关键字访问继承自父类的属性
和方法
。
● 在子类中可以使用super
关键字访问父类定义的方法
。
访问修饰符
访问修饰符(Access Modifiers
)用于控制类成员(属性
、方法
等)的可访问性
。TypeScript
提供了三种访问修饰符,分别是private
、protected
和public
。
class Person {
private id: number;
protected name: string;
public age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
}
class Student extends Person {
}
说明
● private
修饰的属性或方法是私有的
,只能在声明它的类中的被访问
。
● protected
修饰的属性或方法是受保护的
,只能在声明它的类和其子类中被访问
。
● public
修饰的属性或方法是公有的
,可以在任何地方
被访问到,默认所有的属性和方法都是 public
的。
接口(interface)
概述
接口(interface
)是面向对象编程中的另一个重要概念。接口通常会作为一种契约或规范让类(class
)去遵守,确保类实现某些特定的行为或功能。
接口定义
接口使用interface
关键字定义,通常情况下,接口中只会包含属性
和方法
的声明,而不包含具体的实现细节
,具体的细节由其实现类完成。
interface Person {
id: number;
name: string;
age: number;
introduce(): void;
}
接口实现
接口的实现需要用到implements
关键字,实现类中,需要包含接口属性的赋值逻辑,以及接口方法的实现逻辑。
class Student implements Person {
id: number;
name: string;
age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
introduce(): void {
console.log('Hello,I am a student');
}
}
多态
多态
是面相对象编程中的一个重要概念,它可以使同一类型的对象具有不同的行为
。下面我们通过一个具体的案例来体会多态这一概念
首先,再创建一个Person
接口的实现类Teacher
,如下
class Teacher implements Person {
id: number;
name: string;
age: number;
constructor(id: number, name: string, age: number) {
this.id = id;
this.name = name;
this.age = age;
}
introduce(): void {
console.log('Hello,I am a teacher');
}
}
然后分别创建一个Student
对象和一个Teacher
对象,注意两个对象的类型均可以设置Person
,如下
let p1: Person = new Student(1, 'zhangsan', 17);
let p2: Person = new Teacher(2, 'lisi', 35);
接口的作用
在传统的面向对象编程的场景中,接口主要用于设计和组织代码,使代码更加容易扩展和维护
,下面举例说明。
假如现在需要实现一个订单支付系统,按照面向对象编程的习惯,首先需要定义一个订单类(Order
),如下
class Order {
totalAmount: number;
constructor(totalAmount: number) {
this.totalAmount = totalAmount;
}
pay() {
console.log(`AliPay:${this.totalAmount}`);
}
}
很容易预想到,这个系统将来可能需要支持其他的支付方式,为了方便代码支持新的支付方式,我们可以对代码进行如下改造。
首先定义一个支付策略的接口,接口中声明一个pay
方法,用来规范实现类必须实现支付逻辑。
interface PaymentStrategy {
pay(amount: number): void;
}
然后在订单类中增加一个PaymentStrategy
的属性,并且在订单类中的pay
方法中调用PaymentStrategy
的pay
方法,如下
class Order {
totalAmount: number;
paymentStrategy: PaymentStrategy;
constructor(totalAmount: number, paymentStrategy: PaymentStrategy) {
this.totalAmount = totalAmount;
this.paymentStrategy = paymentStrategy;
}
pay() {
this.paymentStrategy.pay(this.totalAmount);
}
}
这样改造完之后,就可以很容易的在不改变现有代码的情况下,支持新的支付方式了。
比如现在需要支持AliPay
,那我们就可以创建AliPay
这个类(class
)并实现(implement
)PaymentStrategy
这个接口,如下
class AliPay implements PaymentStrategy {
pay(amount: number): void {
console.log(`AliPay:${amount}`);
}
}
这样一来,之后创建的订单就可以使用AliPay
这个支付方式了。
let order = new Order(1000,new AliPay());
order.pay();
TS 中的接口的特殊性
TypeScript
中的接口是一个非常灵活的概念,除了用作类的规范之外,也常用于直接描述对象的类型,例如,现有一个变量的定义如下
let person: {name:string, age:number, gender:string} = {name:'张三', age:10, gender:'男'};
可以看到变量的值为一个一般对象,变量的类型为{name:string, age:number, gender:string}
,此时就可以声明一个接口来描述该对象的类型,如下
interface Person {
name: string;
age: number;
gender: string;
}
let person: Person = {name:'张三', age:10, gender:'男'};
枚举
概念
枚举(Enumeration
)是编程语言中常见的一种数据类型,其主要功能是定义一组有限的选项,例如,方向(上、下、左、右)
或季节(春、夏、秋、冬)
等概念都可以使用枚举类型定义。
枚举定义
枚举的定义需使用enum
关键字,如下
enum Season {
SPRING,
SUMMER,
AUTUMN,
WINTER
}
枚举使用
枚举的使用记住两个原则即可
○ 枚举值的访问
像访问对象属性一样访问枚举值,例如Season.SPRING
○ 枚举值的类型
枚举值的类型为enum
的名称,例如Season.SPRING
和Season.SUMMER
等值的类型都是Season
let spring:Season = Season.SPRING;
使用场景
现需要编写一个函数move
,其功能是根据输入的方向(上、下、左、右)
进行移动,此时就可以先使用枚举定义好所有可能的输入选项,如下
enum Direction {
UP,
BOTTOM,
LEFT,
RIGHT
}
move
函数的实现如下
function move(direction: Direction) {
if(direction===Direction.UP){
console.log('向上移动');
}else if(direction===Direction.BOTTOM){
console.log('向下移动');
}else if(direction===Direction.LEFT){
console.log('向左移动');
}else{
console.log('向右移动');
}
}
move(Direction.UP);
赋值
在TypeScript
中,枚举实际上是一个对象,而每个枚举值都是该对象的一个属性,并且每个属性都有具体的值,属性值只支持两种类型——数字
或字符串
。
默认情况下,每个属性的值都是数字,并且从0
开始递增,例如上述案例中的Direction
枚举中,Direction.UP
的值为0
,Direction.BOTTOM
的值为1
,依次类推,具体如下
console.log(Direction.UP) //0
console.log(Direction.BOTTOM) //1
console.log(Direction.LEFT) //2
console.log(Direction.RIGHT) //3
除了使用默认的数字作为属性的值,我们还能手动为每个属性赋值,例如
enum Direction {
UP = 1,
BOTTOM = 2,
LEFT = 3,
RIGHT = 4
}
console.log(Direction.UP) //1
console.log(Direction.BOTTOM) //2
console.log(Direction.LEFT) //3
console.log(Direction.RIGHT) //4
再例如
enum Direction {
UP = 'up',
BOTTOM = 'bottom',
LEFT = 'left',
RIGHT = 'right'
}
console.log(Direction.UP) //up
console.log(Direction.BOTTOM) //bottom
console.log(Direction.LEFT) //left
console.log(Direction.RIGHT) //right
通过为枚举属性赋值,可以赋予枚举属性一些更有意义的信息,例如以下枚举
enum Color {
Red = 0xFF0000,
Green = 0x00FF00,
Blue = 0x0000FF
}
enum FontSize {
Small = 12,
Medium = 16,
Large = 20,
ExtraLarge = 24
}
模块化
概述
模块化
是指将复杂的程序拆解为多个独立的文件单元,每个文件被称为一个模块。在 TypeScript
中,默认情况下,每个模块都拥有自己的作用域,这意味着在一个模块中声明的任何内容(如变量
、函数
、类
等)在该模块外部是不可见的。为了在一个模块中使用其他模块的内容,必须对这些内容进行导入、导出。
导出
导出须使用export
关键字,语法如下
export function hello() {
console.log('hello module A');
}
export const str = 'hello world';
const num = 1;
导入
导入须使用import
关键字,语法如下
import { hello, str } from './moduleA';
hello();
console.log(str);
避免命名冲突
若多个模块中具有命名相同的变量、函数等内容,将这些内容导入到同一模块下就会出现命名冲突。解决办法有多种
1.导入重命名
import { hello as helloFromA, str as strFromA } from "./moduleA";
import { hello as helloFromC, str as strFromC } from "./moduleC";
helloFromA();
console.log(strFromA);
helloFromC();
console.log(strFromC);
2.创建模块对象
上述导入重命名的方式能够很好的解决命名冲突的问题,但是当冲突内容较多时,这种写法会比较冗长。除了导入重命名外,还可以将某个模块的内容统一导入到一个模块对象上,这样就能简洁有效的解决命名冲突的问题了,具体语法如下
import * as A from "./moduleA";
import * as C from "./moduleC";
A.hello();
console.log(A.str);
C.hello();
console.log(C.str);
默认导入导出
除了上述导入导出的语法之外,还有一种语法,叫做默认导入导出,这种语法相对简洁一些。
● 默认导出
默认导出允许一个模块指定一个(最多一个)默认的导出项,语法如下
export default function hello(){
console.log('moduleA');
}
● 默认导入
由于每个模块最多有一个默认导出,因此默认导入无需关注导入项的原始名称,并且无需使用{}
。
import helloFromA from "./moduleA";
由于默认导入时无需关注导入项的名称,所以默认导出支持匿名内容,比如匿名函数,语法如下
export default function () {
console.log('moduleB');
}