ArkTS基础语法 |(3)类和接口
在学习HarmonyOS开发的核心语言ArkTS时,整理了一份基础语法笔记,方便日后回顾。
一、类的基础定义与实例化
类是ArkTS面向对象编程的核心载体,用于封装数据(字段)和行为(方法)。
类声明会引入一个新类型,并定义其字段、方法和构造函数,核心类成员包含实例字段 、实例方法 、构造函数。
定义类后,可通过关键字new 或对象字面量两种方式创建类的实例。
new 适合带构造函数的类,对象字面量适合无自定义构造函数的简单类。
1. 关键字 new 创建实例(带构造函数)
TypeScript
// 定义包含构造函数、实例字段、实例方法的Person类
class Person {
name: string = ''; // 实例字段 显式初始化
surname: string = ''; // 实例字段 显式初始化
// 构造函数:初始化实例字段
constructor (n: string, sn: string) {
this.name = n;
this.surname = sn;
}
// 实例方法:拼接姓名
fullName(): string {
return this.name + ' ' + this.surname;
}
}
// new关键字创建实例并调用方法
let p = new Person('John', 'Smith');
console.info(p.fullName()); // 输出:John Smith
2. 对象字面量创建实例(无自定义构造函数)
TypeScript
// 定义简单的Point类 仅包含实例字段
class Point {
x: number = 0;
y: number = 0;
}
// 对象字面量创建实例 直接赋值字段
let p: Point = {x: 42, y: 42};
注意 :对象字面量仅能在类型可推导的上下文中使用,且赋值的字段需与类的字段完全匹配。
二、类的字段
字段是类中声明的变量,用于存储数据,ArkTS中将类的字段分为实例字段 和静态字段 ,同时要求所有字段必须显式初始化 ,且支持通过getter/setter实现属性的受控访问。
1. 实例字段与静态字段
实例字段
-
属于类的每个实例,每个实例拥有独立的实例字段集合。
-
必须通过类的实例访问,不能通过类名直接访问。
-
声明时需显式初始化,或在构造函数中完成初始化。
静态字段
-
使用关键字static 声明,属于类本身,所有实例共享同一个静态字段。
-
必须通过类名访问,不能通过实例访问。
-
声明时需显式初始化。
TypeScript
// 示例:实例字段与静态字段的使用
class Student {
// 实例字段:每个学生的独立姓名
name: string = '';
// 静态字段:所有学生的共同学校(共享值)
static school: string = 'HarmonyOS大学';
constructor(n: string) {
this.name = n;
}
}
// 创建两个实例
let s1 = new Student('小李');
let s2 = new Student('小何');
// 通过实例访问实例字段
console.log(s1.name); // 输出:小李
// 通过类名访问静态字段
console.log(Student.school); // 输出:HarmonyOS大学
// 修改静态字段 所有实例共享修改后的值
Student.school = '鸿蒙大学';
console.log(Student.school); // 输出:鸿蒙大学
2. 字段初始化规则
ArkTS要求所有字段必须在声明时或构造函数中显式初始化 (与标准TypeScript的strictPropertyInitialization模式一致),未初始化的字段会导致编译错误 ,该规则可减少运行时错误,提升程序执行性能。
3. getter和setter
getter/setter用于对类的属性进行受控访问,可在属性赋值/获取时增加逻辑校验,替代直接的字段访问。
在类中可单独定义getter、单独定义setter,或二者组合定义。
TypeScript
// 示例:getter/setter的使用
class Circle {
private _radius: number = 0; // 私有字段 仅类内部可访问
// getter:获取半径 增加非负校验
get radius(): number {
return this._radius < 0 ? 0 : this._radius;
}
// setter:设置半径 过滤负数
set radius(r: number) {
this._radius = r >= 0 ? r : 0;
}
}
let c = new Circle();
c.radius = -5; // 赋值负数 被setter过滤
console.log(c.radius); // 输出:0
c.radius = 10;
console.log(c.radius); // 输出:10
三、类的方法
方法是类中封装的行为逻辑,ArkTS中将类的方法分为实例方法 和静态方法 ,二者的访问范围、调用方式存在明显区别。
1. 实例方法
-
属于类的实例,必须通过类的实例调用。
-
可访问实例字段 、静态字段,包括类的私有字段。
-
是类最常用的方法类型,用于实现实例的具体行为。
TypeScript
// 示例:实例方法计算矩形面积
class RectangleSize {
// 私有实例字段 仅类内部可访问
private height: number = 0;
private width: number = 0;
// 构造函数初始化私有字段
constructor(height: number, width: number) {
this.height = height;
this.width = width;
}
// 实例方法:计算面积 可访问私有字段
calculateArea(): number {
return this.height * this.width;
}
}
// 实例化后调用实例方法
let square = new RectangleSize(10, 10);
console.log(square.calculateArea()); // 输出:100
2. 静态方法
-
使用关键字static 声明,属于类本身 ,必须通过类名调用。
-
仅能访问静态字段/其他静态方法,无法访问实例字段(因无具体实例)。
-
用于实现类的公共行为,与实例无关。
TypeScript
// 示例:静态方法的定义与调用
class Tool {
// 静态方法:实现通用的字符串拼接逻辑
static concatStr(a: string, b: string): string {
return a + '-' + b;
}
}
// 通过类名直接调用静态方法
console.info(Tool.concatStr('ArkTS', 'HarmonyOS')); // 输出:ArkTS-HarmonyOS
3. 方法重载签名
方法重载允许为同一个方法 定义多个不同的签名 (参数类型/个数不同),实现方法的多场景调用,需遵循重载签名在前、实现签名在后的规则,实现签名需兼容所有重载签名的参数类型。
注意 :若两个重载签名的名称和参数列表完全相同,会导致编译错误。
TypeScript
// 示例:方法重载签名
class C {
// 重载签名1:参数为number类型
foo(x: number): void;
// 重载签名2:参数为string类型
foo(x: string): void;
// 实现签名:兼容number | string类型,编写具体逻辑
foo(x: number | string): void {
console.log('参数值:', x);
}
}
let c = new C();
c.foo(123); // 匹配重载签名1
c.foo('aa'); // 匹配重载签名2
四、类的继承
ArkTS支持单继承 ,即一个类只能继承一个基类(父类/超类),同时支持实现多个接口。
子类(派生类)会继承父类的字段和方法(构造函数除外),并可新增字段/方法,或重写 父类的方法。
1. 继承的语法格式
TypeScript
class 子类名 extends 父类名 implements 接口列表 {
// 子类的字段、方法、构造函数
}
2. 核心特性
(1) 继承内容 :子类继承父类的实例字段、静态字段、实例方法、静态方法,不继承构造函数。
(2) 方法重写 :子类可重写父类的方法,重写的方法必须与原方法参数类型一致,返回类型为原类型或其子类型。
(3) 父类访问 :通过关键字super访问父类的方法和构造函数,子类构造函数的第一条语句必须是super()(调用父类构造函数)。
(4) 接口实现 :包含implements子句的类,必须实现接口中所有未定义默认实现的方法。
3. 关键示例
(1)子类继承与super调用
TypeScript
// 父类:矩形
class RectangleSize {
width: number = 0;
height: number = 0;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
// 父类方法:计算面积
calculateArea(): number {
return this.width * this.height;
}
}
// 子类:正方形 继承自矩形
class Square extends RectangleSize {
// 子类构造函数:必须先调用super()
constructor(side: number) {
super(side, side); // 调用父类构造函数
}
}
// 子类实例化 继承父类方法
let square = new Square(5);
console.log(square.calculateArea()); // 输出:25
(2)方法重写
TypeScript
class Animal {
name: string = '';
constructor(n: string) {
this.name = n;
}
// 父类方法
say(): string {
return '动物的叫声';
}
}
// 子类:狗 重写父类say方法
class Dog extends Animal {
constructor(n: string) {
super(n);
}
// 方法重写:参数类型一致\返回类型一致
say(): string {
return this.name + ':汪汪汪';
}
}
let dog = new Dog('旺财');
console.log(dog.say()); // 输出:旺财:汪汪汪
五、构造函数
构造函数用于初始化类的实例状态,是创建实例时自动执行的方法。
1. 基础语法
TypeScript
constructor ([参数列表]) {
// 初始化逻辑,通常为实例字段赋值
}
2. 核心规则
(1) 默认构造函数 :若类未显式定义构造函数,编译器会自动创建空参数的默认构造函数,使用字段类型的默认值初始化实例。
(2) 子类构造函数 :子类必须通过super()显式调用父类的构造函数,且super()必须是子类构造函数体的第一条语句。
(3) 显式初始化:构造函数是实例字段初始化的重要位置,未在声明时初始化的字段,必须在构造函数中完成。
(4) 无返回值 :构造函数无需指定返回值类型,也不会返回任何值。
3. 构造函数重载签名
与方法重载类似,构造函数也支持重载,通过多个不同的重载签名指定构造函数的不同调用方式,实现签名需兼容所有重载签名。
TypeScript
// 示例:构造函数重载
class C {
// 重载签名1:参数为number类型
constructor(x: number);
// 重载签名2:参数为string类型
constructor(x: string);
// 实现签名:兼容number | string类型
constructor(x: number | string) {
console.log('构造函数参数:', x);
}
}
// 匹配不同的重载签名创建实例
let c1 = new C(123); // 重载签名1
let c2 = new C('abc'); // 重载签名2
六、可见性修饰符
ArkTS为类的字段和方法提供了可见性修饰符 ,用于控制类成员的访问范围,实现封装性。 修饰符包括private、protected、public,默认可见性为public。
1. 公有(public)
-
最宽松的修饰符,默认所有成员均为
public。 -
public修饰的成员,在程序任何可访问该类的地方都能访问(类内部、实例、子类)。
2. 私有(private)
-
最严格的修饰符。
-
private修饰的成员,仅能在声明该成员的类内部访问,类外部、子类均无法访问。
3. 受保护(protected)
-
介于
public和private之间。 -
protected修饰的成员,类内部和子类中可访问,类外部无法访问。
4. 完整示例
TypeScript
// 父类
class Base {
public a: string = ''; // 公有
private b: string = ''; // 私有
protected c: string = ''; // 受保护
constructor() {
this.a = 'public';
this.b = 'private';
this.c = 'protected';
}
// 类内部可访问所有修饰符的成员
showBase() {
console.log(this.a, this.b, this.c);
}
}
// 子类
class Derived extends Base {
showDerived() {
console.log(this.a); // public可访问
// console.log(this.b); // 编译错误,private子类不可访问
console.log(this.c); // protected子类可访问
}
}
// 实例化
let base = new Base();
let derived = new Derived();
// 类外部访问
console.log(base.a); // public可访问
// console.log(base.b); // 编译错误 private外部不可访问
// console.log(base.c); // 编译错误 protected外部不可访问
七、对象字面量
对象字面量是创建类实例的便捷方式,通过 {属性名: 值} 的形式直接初始化实例,同时也可用于初始化泛型Record类型 ,是ArkTS中创建对象的常用语法。
1. 类实例的对象字面量
TypeScript
// 定义简单的Point类 仅包含实例字段
class Point {
x: number = 0;
y: number = 0;
}
// 对象字面量创建实例 直接赋值字段
let p: Point = {x: 42, y: 42};
核心要求是字段与类完全匹配 、类型可推导 。
2. Record类型的对象字面量
泛型Record<K, V>用于将键类型K 映射到值类型V ,常通过对象字面量初始化,其中K仅支持字符串类型 或数值类型 (不包括BigInt),V可为任意类型。
TypeScript
// 示例:Record类型的对象字面量
// 定义Record类型:键为string 值为number
let userAge: Record<string, number> = {
'John': 25,
'Mary': 21,
'Tom': 23
};
// 访问值
console.log(userAge['John']); // 输出:25
console.log(userAge.Mary); // 输出:21
// 键为数值类型的Record
let score: Record<number, string> = {
100: '优秀',
80: '良好',
60: '及格'
};
console.log(score[80]); // 输出:良好
八、抽象类
带有abstract 修饰符的类称为抽象类,抽象类是对一组具体类的通用特性抽象 ,无法直接实例化,主要用于作为父类被子类继承,同时支持定义抽象方法 。
1. 核心特性
(1)不可实例化 :直接通过new创建抽象类实例会导致编译错误。
(2)可被继承 :抽象类的子类可以是抽象类,也可以是非抽象类,非抽象子类可实例化。
(3)可包含普通成员:抽象类中可定义普通的实例字段、静态字段、实例方法、静态方法。
(4)可包含构造函数 :抽象类的构造函数可用于初始化子类的公共字段,子类通过super()调用。
2. 抽象方法
带有abstract 修饰符的方法称为抽象方法,仅能在抽象类中声明。
核心特性:
(1)仅声明,无实现:抽象方法只有方法签名,没有方法体。
(2)子类必须实现:抽象类的非抽象子类,必须实现父类中所有的抽象方法。
(3)不可单独存在 :非抽象类中声明抽象方法会导致编译错误。
3. 完整示例
TypeScript
// 抽象类:形状
abstract class Shape {
color: string = '';
// 抽象类的构造函数
constructor(c: string) {
this.color = c;
}
// 普通方法:已有实现
showColor(): void {
console.log('形状颜色:', this.color);
}
// 抽象方法:仅声明 无实现 子类必须重写
abstract calculateArea(): number;
}
// 非抽象子类:圆形 继承抽象类
class Circle extends Shape {
radius: number = 0;
constructor(c: string, r: number) {
super(c); // 调用抽象类构造函数
this.radius = r;
}
// 必须实现父类的抽象方法
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
// 实例化子类(抽象类无法实例化)
let circle = new Circle('red', 5);
circle.showColor(); // 输出:形状颜色:red
console.log(circle.calculateArea()); // 输出:78.53981633974483
// let shape = new Shape('blue'); // 编译错误:抽象类无法创建实例
// 编译错误:非抽象类声明抽象方法
// class Test {
// abstract test(): void;
// }
九、接口
接口通过interface关键字声明,是代码协定的定义方式 ,用于约定类的属性和方法,实现多态特性。
任何类只要实现了接口的所有成员,就可被视为该接口的类型。
ArkTS中接口支持继承 、约定对象结构 、约定对象方法 等特性。
1. 接口的基础定义
接口通常包含属性声明 和方法声明,无具体实现,仅定义"规范",实现接口的类必须严格遵循该规范。
TypeScript
// 示例:基础接口定义
// 接口1:约定颜色属性
interface Style {
color: string; // 属性声明
}
// 接口2:约定方法
interface AreaSize {
calculateAreaSize(): number; // 方法声明
someMethod(): void; // 方法声明
}
2. 接口的属性声明
接口的属性可以是普通字段 、getter 、setter ,或getter+setter组合。
其中普通字段是getter/setter对的便捷写法,二者完全等价。
TypeScript
// 示例:接口属性的两种等价声明
// 方式1:普通字段形式(便捷写法)
interface Style {
color: string;
}
// 方式2:getter+setter形式
interface Style {
get color(): string;
set color(x: string);
}
// 类实现接口的两种方式
class StyledRectangle implements Style {
// 方式1:直接声明字段
color: string = '';
}
class StyledCircle implements Style {
// 方式2:通过getter/setter实现
private _color: string = '';
get color(): string { return this._color; }
set color(x: string) { this._color = x; }
}
3. 接口继承
接口支持多继承 ,即一个接口可以继承多个其他接口,继承后的接口会包含被继承接口的所有成员,并可新增自己的成员。
TypeScript
// 示例:接口继承
// 父接口1
interface Style {
color: string;
}
// 父接口2
interface Size {
width: number;
height: number;
}
// 子接口:继承Style和Size 新增border属性
interface StyledSize extends Style, Size {
border: number;
}
// 实现子接口的类 需实现所有父接口+子接口的成员
class Rectangle implements StyledSize {
color: string = '';
width: number = 0;
height: number = 0;
border: number = 0;
}
4. 接口约定对象结构与方法
ArkTS中通过接口严格约定对象的结构 和方法类型,是定义自定义对象类型的核心方式,适用于非类实例的普通对象。
核心规则:
- 接口内属性之间不加逗号 ,对象内属性之间加逗号。
- 对象的属性/方法必须与接口一一对应,不可多也不可少。
(1)接口定义对象结构
TypeScript
// 定义接口:约定User对象的属性名和类型(属性间无逗号)
interface User {
name: string
age: number
isVip: boolean
}
// 基于接口创建对象:属性与接口一一对应(属性间加逗号)
let user1: User = {
name: '小鸣',
age: 20,
isVip: true
};
// 访问对象属性
console.log(user1.name); // 输出:小鸣
console.log(user1.age); // 输出:20
(2)接口约定对象方法
对象方法用于描述对象的行为,接口中通过箭头函数约定方法的参数类型和返回值类型,实现对象时需严格遵循该约定。
-
无参数方法:
方法名: () => 返回值类型 -
无返回值方法:返回值类型为
void -
带参数方法:
方法名: (参数1:类型1, 参数2:类型2) => 返回值类型
TypeScript
// 示例:接口约定对象方法
// 定义接口:约定属性和方法
interface User {
name: string
// 无参数、无返回值方法
sayName: () => void
// 带1个string参数 返回string类型方法
getAgeDesc: (prefix: string) => string
// 带2个参数 返回number类型方法
sum: (a: number, b: number) => number
}
// 基于接口实现对象
let user2: User = {
name: '小何',
// 实现无参数方法
sayName: () => {
console.log('我的名字是' + user2.name);
},
// 实现带参数方法
getAgeDesc: (prefix: string) => {
return prefix + '20岁';
},
// 实现多参数方法
sum: (a: number, b: number) => {
return a + b;
}
};
// 调用对象方法
user2.sayName(); // 输出:我的名字是小何
let desc = user2.getAgeDesc('年龄:');
console.log(desc); // 输出:年龄:20岁
console.log(user2.sum(10, 20)); // 输出:30
5. 抽象类与接口的区别
抽象类和接口均无法直接实例化,且都用于实现代码的抽象和复用,但二者在设计目的、语法规则上有本质区别。
在ArkTS中,抽象类和接口的核心区别如下表:
| 特性 | 抽象类(abstract class) | 接口(interface) |
|---|---|---|
| 继承/实现规则 | 类仅能单继承一个抽象类 | 类可实现多个接口 |
| 成员实现 | 可包含方法的具体实现,也可包含抽象方法 | 无任何方法实现,仅声明方法/属性 |
| 静态成员 | 可包含静态字段、静态方法、静态代码块 | 不可包含任何静态成员 |
| 构造函数 | 可定义构造函数,子类通过super()调用 | 不可定义构造函数 |
| 设计目的 | 对类的抽象,捕捉子类的通用特性 | 对行为的抽象,定义代码协定 |
| 字段/属性 | 可声明实例字段并初始化 | 仅声明属性类型,不可初始化 |