摄影网站公司怎么优化网站排名才能起来
TypeScript 中的类(Class)是面向对象编程(OOP)的核心,它基于 ES6 类的语法,并引入了类型系统和额外特性(如访问修饰符、抽象类等)。以下我为总结出了几点:
一、类的简单介绍
1. 定义类
class Person {// 属性(需初始化或通过构造函数赋值)name: string;age: number;// 构造函数constructor(name: string, age: number) {this.name = name;this.age = age;}// 方法greet() {console.log(`Hello, I'm ${this.name}.`);}
}// 使用
const alice = new Person("Alice", 25);
alice.greet(); // 输出:Hello, I'm Alice.
类的方法跟普通函数一样,可以使用参数默认值,以及函数重载(详情可阅读上一篇文章)。
class Point {x: number;y: number;constructor(x = 0, y = 0) {this.x = x;this.y = y;}
}
上面示例中,如果新建实例时,不提供属性x
和y
的值,它们都等于默认值0
。
下面是函数重载的例子。
class Point {constructor(x:number, y:string);constructor(s:string);constructor(xs:number|string, y?:string) {// ...}
}
上面示例中,构造方法可以接受一个参数,也可以接受两个参数,采用函数重载进行类型声明。
2.存取器方法
存取器(accessor)是特殊的类方法,包括取值器(getter)和存值器(setter)两种方法。
它们用于读写某个属性,取值器用来读取属性,存值器用来写入属性。
class C {_name = '';get name() {return this._name;}set name(value) {this._name = value;}
}
上面示例中,get name()
是取值器,其中get
是关键词,name
是属性名。外部读取name
属性时,实例对象会自动调用这个方法,该方法的返回值就是name
属性的值。
set name()
是存值器,其中set
是关键词,name
是属性名。外部写入name
属性时,实例对象会自动调用这个方法,并将所赋的值作为函数参数传入。
TypeScript 对存取器有以下规则。
(1)如果某个属性只有get
方法,没有set
方法,那么该属性自动成为只读属性。
class C {_name = 'foo';get name() {return this._name;}
}
const c = new C();
c.name = 'bar'; // 报错
上面示例中,name
属性没有set
方法,对该属性赋值就会报错。
(2)set
方法的参数类型,必须兼容get
方法的返回值类型,否则报错。
class C {_name = '';get name():string {return this._name;}set name(value:number) {this._name = value; // 报错}
}
上面示例中,get
方法的返回值类型是字符串,与set
方法参数类型不兼容,导致报错。
class C {_name = '';get name():string {return this._name;}set name(value:number|string) {this._name = String(value); // 正确}
}
上面示例中,set
方法的参数类型(number|return
)兼容get
方法的返回值类型(string
),这是允许的。但是,最终赋值的时候,还是必须保证与get
方法的返回值类型一致。
另外,如果set
方法的参数没有指定类型,那么会推断为与get
方法返回值类型一致。
(3)get
方法与set
方法的可访问性必须一致,要么都为公开方法,要么都为私有方法。
3.属性索引
类允许定义属性索引。
class MyClass {[s:string]: boolean |((s:string) => boolean);get(s:string) {return this[s] as boolean;}
}
上述代码表示:
- 表示该类的实例允许任意字符串类型的属性名
- 属性的值可以是两种类型:
boolean
类型- 函数类型:接收 string 参数并返回 boolean 的函数
这里给出几个示例方便大家理解:
const obj = new MyClass();// 设置属性(符合索引签名要求)
obj.isActive = true; // ✅ 允许 boolean 值
obj.checkValid = (s: string) => s.length > 0; // ✅ 允许函数// 使用 get 方法
console.log(obj.get('isActive')); // ✅ 输出 true
console.log(obj.get('checkValid')); // ❌ 运行时错误:函数无法被当作 boolean 使用// 类型错误示例
obj.count = 10; // ❌ 类型错误:必须是 boolean 或函数类型
二、类与接口
1.implements 关键字
interface 接口或 type 别名,可以用对象的形式,为 class 指定一组检查条件。然后,类使用 implements 关键字,表示当前类满足这些外部类型条件的限制。
interface Country {name:string;capital:string;
}
// 或者
type Country = {name:string;capital:string;
}
class MyCountry implements Country {name = '';capital = '';
}
上面示例中,interface
或type
都可以定义一个对象类型。类MyCountry
使用implements
关键字,表示该类的实例对象满足这个外部类型。
类可以定义接口没有声明的方法和属性。
interface Point {x: number;y: number;
}
class MyPoint implements Point {x = 1;y = 1;z:number = 1;
}
implements
关键字后面,不仅可以是接口,也可以是另一个类。这时,后面的类将被当作接口。
class Car {id:number = 1;move():void {};
}
class MyCar implements Car {id = 2; // 不可省略move():void {}; // 不可省略
}
上面示例中,implements
后面是类Car
,这时 TypeScript 就把Car
视为一个接口,要求MyCar
实现Car
里面的每一个属性和方法,否则就会报错。所以,这时不能因为Car
类已经实现过一次,而在MyCar
类省略属性或方法。
2.多接口的实现
类的继承
class Car implements MotorVehicle {
}
class SecretCar extends Car implements Flyable, Swimmable {
}
上面示例中,Car
类实现了MotorVehicle
,而SecretCar
类继承了Car
类,然后再实现Flyable
和Swimmable
两个接口,相当于SecretCar
类同时实现了三个接口。
接口的继承
interface MotorVehicle {// ...
}
interface Flyable {// ...
}
interface Swimmable {// ...
}
interface SuperCar extends MotoVehicle,Flyable, Swimmable {// ...
}
class SecretCar implements SuperCar {// ...
}
上面示例中,接口SuperCar
通过SuperCar
接口,就间接实现了多个接口。
3.类与接口的合并
TypeScript 不允许两个同名的类,但是如果一个类和一个接口同名,那么接口会被合并进类。
class A {x:number = 1;
}
interface A {y:number;
}
let a = new A();
a.y = 10;
a.x // 1
a.y // 10
上面示例中,类A
与接口A
同名,后者会被合并进前者的类型定义。
三、Class 类型
1.实例类型
TypeScript 的类本身就是一种类型,但是它代表该类的实例类型,而不是 class 的自身类型。
// 定义 Color 类
class Color {name: string; // 声明类的字符串类型属性// 构造函数:接收字符串参数,用于初始化 name 属性constructor(name: string) {this.name = name;}
}// 创建实例
const green: Color = new Color('green');
// 等效于:
// const green = new Color('green'); // TypeScript 可以自动推断类型
上述代码的实例化过程:
new Color('green') 的执行过程:
1. 创建空对象 {}
2. 调用构造函数 constructor('green')
3. 设置 this.name = 'green'
4. 返回初始化后的对象
类型检查示例:
green.name = 'red'; // ✅ 允许修改字符串值
green.name = 123; // ❌ 类型错误:不能将 number 赋值给 stringconst wrong: Color = { name: 'blue' };
// ❌ 错误:虽然对象结构相同,但缺少构造函数初始化过程
2.结构类型原则
Class 也遵循“结构类型原则”。一个对象只要满足 Class 的实例结构,就跟该 Class 属于同一个类型。
class Foo {id!:number;
}
function fn(arg:Foo) {// ...
}
const bar = {id: 10,amount: 100,
};
fn(bar); // 正确
上面示例中,对象bar
满足类Foo
的实例结构,只是多了一个属性amount
。所以,它可以当作参数,传入函数fn()
。
如果两个类的实例结构相同,那么这两个类就是兼容的,可以用在对方的使用场合。即使其中的一个类添加了一个属性,,TypeScript 也会认为是同一个类型。
class Person {name: string;
}
class Customer {name: string;
}
class Man {name: string;age: number;
}// 正确
const cust:Customer = new Person();
// 正确
const cust:Customer = new Man();
但是注意,如果Man
类比Customer
类少一个属性age
,它就不满足Customer
类型的实例结构,就报错了。因为在使用Customer
类型的情况下,可能会用到它的age
属性,而Person
类就没有这个属性。
class Man{name: string;
}
class Customer {name: string;age: number;
}
// 报错
const cust:Customer = new Man();
不仅是类,如果某个对象跟某个 class 的实例结构相同,TypeScript 也认为两者的类型相同。
注意,确定两个类的兼容关系时,只检查实例成员,不考虑静态成员和构造方法。
class Point {x: number;y: number;static t: number;constructor(x:number) {}
}
class Position {x: number;y: number;z: number;constructor(x:string) {}
}
const point:Point = new Position('');
上面示例中,Point
与Position
的静态属性和构造方法都不一样,但因为Point
的实例成员与Position
相同,所以Position
兼容Point
。
四、类的继承
类(这里又称“子类”)可以使用 extends 关键字继承另一个类(这里又称“基类”)的所有属性和方法。
class A {greet() {console.log('Hello, world!');}
}
class B extends A {
}
const b = new B();
b.greet() // "Hello, world!"
子类可以覆盖基类的同名方法。
class B extends A {greet(name?: string) {if (name === undefined) {super.greet();} else {console.log(`Hello, ${name}`);}}
}
但是,子类的同名方法不能与基类的类型定义相冲突。
class A {greet() {console.log('Hello, world!');}
}
class B extends A {// 报错greet(name:string) {console.log(`Hello, ${name}`);}
}
上面示例中,子类B
的greet()
有一个name
参数,跟基类A
的greet()
定义不兼容,因此就报错了。
五、访问修饰符
TypeScript 提供三种访问控制修饰符:
public
(默认):任何地方可访问。private
:仅类内部可访问。protected
:类内部和子类可访问。
public
public
修饰符表示这是公开成员,外部可以自由访问。
class Greeter {public greet() {console.log("hi!");}
}
const g = new Greeter();
g.greet();
private
private
修饰符表示私有成员,只能用在当前类的内部,类的实例和子类都不能使用该成员。
class A {private x:number = 0;
}
const a = new A();
a.x // 报错
class B extends A {showX() {console.log(this.x); // 报错}
}
严格地说,
private
定义的私有成员,并不是真正意义的私有成员。一方面,编译成 JavaScript 后,private
关键字就被剥离了,这时外部访问该成员就不会报错。另一方面,由于前一个原因,TypeScript 对于访问private
成员没有严格禁止,使用方括号写法([]
)或者in
运算符,实例对象就能访问该成员。
class A {private x = 1;
}
const a = new A();
a['x'] // 1
if ('x' in a) { // 正确// ...
}
由于private
存在这些问题,加上它是 ES6 标准发布前出台的,而 ES6 引入了自己的私有成员写法#propName
。因此建议不使用private
,改用 ES6 的写法,获得真正意义的私有成员。
class A {#x = 1;
}
const a = new A();
a['x'] // 报错
protected
protected
修饰符表示该成员是保护成员,只能在类的内部使用该成员,实例无法使用该成员,但是子类内部可以使用。
class A {protected x = 1;
}
class B extends A {getX() {return this.x;}
}
const a = new A();
const b = new B();
a.x // 报错
b.getX() // 1
子类不仅可以拿到父类的保护成员,还可以定义同名成员。
class A {protected x = 1;
}
class B extends A {x = 2;
}
上面示例中,子类B
定义了父类A
的同名成员x
,并且父类的x
是保护成员,子类将其改成了公开成员。B
类的x
属性前面没有修饰符,等同于修饰符是public
,外界可以读取这个属性。
在类的外部,实例对象不能读取保护成员,但是在类的内部可以。
class A {protected x = 1;f(obj:A) {console.log(obj.x);}
}
const a = new A();
a.x // 报错
a.f(a) // 1
六、静态成员
静态成员属于类本身而非实例,通过 static
定义:
class MathUtils {static PI = 3.1415926;static sum(a: number, b: number) {return a + b;}
}console.log(MathUtils.PI); // 3.1415926
console.log(MathUtils.sum(1, 2)); // 3
静态成员是只能通过类本身使用的成员,不能通过实例对象使用。
class Demo {static staticVal = 10; // 静态属性instanceVal = 20; // 实例属性static x = 0;static printX() {console.log(Demo.x); // 通过类名访问静态属性}static staticMethod() {console.log(this.staticVal); // ✅ 正确访问静态属性console.log(this.instanceVal); // ❌ 错误:无法访问实例属性}
}// 错误访问方式
// 直接通过类名访问静态属性
Demo.x; // → 0 // 直接通过类名调用静态方法
Demo.printX(); // → 输出 0// 正确访问方式
console.log(Demo.staticVal); // ✅ 10
const obj = new Demo();
console.log(obj.instanceVal); // ✅ 20
七、抽象类
用 abstract
定义抽象类和抽象方法,抽象类不可实例化,抽象方法必须由子类实现:
abstract class A {id = 1;
}
const a = new A(); // 报错
抽象类只能当作基类使用,用来在它的基础上定义子类。
abstract class A {id = 1;
}
class B extends A {amount = 100;
}
const b = new B();
b.id // 1
b.amount // 100
例子实现;
abstract class Shape {abstract area(): number;printArea() {console.log(`Area: ${this.area()}`);}
}class Circle extends Shape {radius: number;constructor(radius: number) {super();this.radius = radius;}area() {return Math.PI * this.radius ** 2;}
}const circle = new Circle(5);
circle.printArea(); // 输出:Area: 78.5398...