一 类的定义

使用 class 关键字来定义一个类,类名通常采用大驼峰命名法。类声明引入一个新类型,并定义其字段、方法和构造函数。
在以下示例中,定义了Person类,该类具有字段name和surname、构造函数和方法fullName:

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.log(p.fullName());

或者,可以使用对象字面量创建实例:

class Point {
  x: number = 0;
  y: number = 0;
}
let p: Point = {x: 42, y: 42};

二、 字段

字段是直接在类中声明的某种类型的变量。
类可以具有实例字段或者静态字段。

2.1 实例字段

实例字段存在于类的每个实例上。每个实例都有自己的实例字段集合。
要访问实例字段,需要使用类的实例。

class Person {
  name: string = '';
  age: number = 0;
  constructor(n: string, a: number) {
    this.name = n;
    this.age = a;
  }

  getName(): string {
    return this.name;
  }
}

let p1 = new Person('Alice', 25);
p1.name;
let p2 = new Person('Bob', 28);
p2.getName();

静态字段

使用关键字static将字段声明为静态。静态字段属于类本身,类的所有实例共享一个静态字段。
要访问静态字段,需要使用类名:

class Person {
  static numberOfPersons = 0;
  constructor() {
     // ...
     Person.numberOfPersons++;
     // ...
  }
}

Person.numberOfPersons;

2.2 字段初始化

为了减少运行时的错误和获得更好的执行性能,ArkTS要求所有字段在声明时或者构造函数中显式初始化。这和标准TS中的strictPropertyInitialization模式一样。以下代码是在ArkTS中不合法的代码。

class Person {
  name: string; // undefined
  
  setName(n:string): void {
    this.name = n;
  }
  
  getName(): string {
    // 开发者使用"string"作为返回类型,这隐藏了name可能为"undefined"的事实。
    // 更合适的做法是将返回类型标注为"string | undefined",以告诉开发者这个API所有可能的返回值。
    return this.name;
  }
}

let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack.getName().length; // 运行时异常:name is undefined

在ArkTS中,应该这样写代码。

class Person {
  name: string = '';
  
  setName(n:string): void {
    this.name = n;
  }
  
  // 类型为'string',不可能为"null"或者"undefined"
  getName(): string {
    return this.name;
  }
}
  

let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"
jack.getName().length; // 0, 没有运行时异常

接下来的代码展示了如果name的值可以是undefined,那么应该如何写代码。

class Person {
  name?: string; // 可能为`undefined`

  setName(n:string): void {
    this.name = n;
  }

  // 编译时错误:name可以是"undefined",所以这个API的返回值类型不能仅定义为string类型
  getNameWrong(): string {
    return this.name;
  }

  getName(): string | undefined { // 返回类型匹配name的类型
    return this.name;
  }
}

let jack = new Person();
// 假设代码中没有对name赋值,例如调用"jack.setName('Jack')"

// 编译时错误:编译器认为下一行代码有可能会访问undefined的属性,报错
jack.getName().length;  // 编译失败

jack.getName()?.length; // 编译成功,没有运行时错误

2.3 getter和setter

setter和getter可用于提供对对象属性的受控访问。
在以下示例中,setter用于禁止将_age属性设置为无效值:

class Person {
  name: string = '';
  private _age: number = 0;
  get age(): number { return this._age; }
  set age(x: number) {
    if (x < 0) {
      throw Error('Invalid age argument');
    }
    this._age = x;
  }
}

let p = new Person();
p.age; // 输出0
p.age = -42; // 设置无效age值会抛出错误

在类中可以定义getter或者setter。

三 方法

方法属于类。类可以定义实例方法或者静态方法。静态方法属于类本身,只能访问静态字段。而实例方法既可以访问静态字段,也可以访问实例字段,包括类的私有字段。

实例方法

以下示例说明了实例方法的工作原理。
calculateArea方法通过将高度乘以宽度来计算矩形的面积:

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);
square.calculateArea(); // 输出:100

静态方法

使用关键字static将方法声明为静态。静态方法属于类本身,只能访问静态字段。
静态方法定义了类作为一个整体的公共行为。属于类本身,而不是类的实例。使用 static 关键字来声明静态方法,可通过类名直接调用。静态方法不能直接访问实例属性和实例方法。
必须通过类名调用静态方法:

class Cl {
  static staticMethod(): string {
    return 'this is a static method.';
  }
}
console.log(Cl.staticMethod());

四 构造函数

构造函数是类中的特殊方法,在创建类的实例时自动调用,用于初始化实例的属性。在 ArkTS 中,使用 constructor 关键字来定义构造函数。

class Person {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

let person = new Person('John', 30);

五 继承

一个类可以继承另一个类(称为基类),并使用以下语法实现多个接口:

class [extends BaseClassName] [implements listOfInterfaces] {
  // ...
}

继承类继承基类的字段和方法,但不继承构造函数。继承类可以新增定义字段和方法,也可以覆盖其基类定义的方法。
基类也称为“父类”或“超类”。继承类也称为“派生类”或“子类”。

继承是面向对象编程的重要特性之一。在 ArkTS 中,使用 extends 关键字来实现继承。

class Person {
    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    introduce() {
        console.log(`My name is ${this.name} and I am ${this.age} years old.`);
    }
}

class Student extends Person {
    grade: string;

    constructor(name: string, age: number, grade: string) {
        super(name, age);
        this.grade = grade;
    }

    study() {
        console.log(`${this.name} is studying in ${this.grade} grade.`);
    }
}

let student = new Student('Alice', 15, '9th');
student.introduce(); 
student.study();     

在上面的示例中,Student 类继承自 Person 类,通过 super 关键字调用父类的构造函数。

包含implements子句的类必须实现列出的接口中定义的所有方法,但使用默认实现定义的方法除外。

interface DateInterface {
  now(): string;
}
class MyDate implements DateInterface {
  now(): string {
    // 在此实现
    return 'now';
  }
}

六 父类访问

关键字super可用于访问父类的实例字段、实例方法和构造函数。在实现子类功能时,可以通过该关键字从父类中获取所需接口:

class RectangleSize {
  protected height: number = 0;
  protected width: number = 0;

  constructor (h: number, w: number) {
    this.height = h;
    this.width = w;
  }

  draw() {
    /* 绘制边界 */
  }
}
class FilledRectangle extends RectangleSize {
  color = ''
  constructor (h: number, w: number, c: string) {
    super(h, w); // 父类构造函数的调用
    this.color = c;
  }

  draw() {
    super.draw(); // 父类方法的调用
    // super.height -可在此处使用
    /* 填充矩形 */
  }
}

七 方法重写

子类可以重写其父类中定义的方法的实现。重写的方法必须具有与原始方法相同的参数类型和相同或派生的返回类型。

class RectangleSize {
  // ...
  area(): number {
    // 实现
    return 0;
  }
}
class Square extends RectangleSize {
  private side: number = 0;
  area(): number {
    return this.side * this.side;
  }
}

八 方法重载签名

通过重载签名,指定方法的不同调用。具体方法为,为同一个方法写入多个同名但签名不同的方法头,方法实现紧随其后。

class C {
  foo(x: number): void;            /* 第一个签名 */
  foo(x: string): void;            /* 第二个签名 */
  foo(x: number | string): void {  /* 实现签名 */
  }
}
let c = new C();
c.foo(123);     // OK,使用第一个签名
c.foo('aa'); // OK,使用第二个签名

如果两个重载签名的名称和参数列表均相同,则为错误。

构造函数重载签名
我们可以通过编写重载签名,指定构造函数的不同调用方式。具体方法为,为同一个构造函数写入多个同名但签名不同的构造函数头,构造函数实现紧随其后。

class C {
  constructor(x: number)             /* 第一个签名 */
  constructor(x: string)             /* 第二个签名 */
  constructor(x: number | string) {  /* 实现签名 */
  }
}
let c1 = new C(123);      // OK,使用第一个签名
let c2 = new C('abc');    // OK,使用第二个签名

如果两个重载签名的名称和参数列表均相同,则为错误。

九 访问修饰符

访问修饰符用于控制类的属性和方法的访问权限。ArkTS 支持三种访问修饰符:public、private 和 protected。

  • public:默认的访问修饰符,可被类的内部、外部和子类访问。
  • private:只能被类的内部访问,不能被类的外部和子类访问。
  • protected:可被类的内部和子类访问,但不能被类的外部访问。

十 抽象类和抽象方法

抽象类是一种不能被实例化的类,通常用于定义一些通用的属性和方法,作为其他类的基类。抽象方法是在抽象类中声明但没有实现的方法,子类必须实现这些抽象方法。

abstract class Shape {
    abstract getArea(): number;
}

class Circle extends Shape {
    radius: number;

    constructor(radius: number) {
        super();
        this.radius = radius;
    }

    getArea(): number {
        return Math.PI * this.radius * this.radius;
    }
}

let circle = new Circle(5);
console.log(circle.getArea()); 

在上面的示例中,Shape 是抽象类,getArea 是抽象方法,Circle 类继承自 Shape 类并实现了 getArea 方法。

十一 对象字面量

对象字面量是一个表达式,可用于创建类实例并提供一些初始值。它在某些情况下更方便,可以用来代替new表达式。
对象字面量的表示方式是:封闭在花括号对({})中的’属性名:值’的列表。

class C {
  n: number = 0;
  s: string = '';
}

let c: C = {n: 42, s: 'foo'};

ArkTS是静态类型语言,如上述示例所示,对象字面量只能在可以推导出该字面量类型的上下文中使用。其他正确的例子:

class C {
  n: number = 0;
  s: string = '';
}

function foo(c: C) {}

let c: C

c = {n: 42, s: 'foo'};  // 使用变量的类型
foo({n: 42, s: 'foo'}); // 使用参数的类型

function bar(): C {
  return {n: 42, s: 'foo'}; // 使用返回类型
}

也可以在数组元素类型或类字段类型中使用:

class C {
  n: number = 0;
  s: string = '';
}
let cc: C[] = [{n: 1, s: 'a'}, {n: 2, s: 'b'}];

Record类型的对象字面量
泛型Record<K, V>用于将类型(键类型)的属性映射到另一个类型(值类型)。常用对象字面量来初始化该类型的值:

let map: Record<string, number> = {
  'John': 25,
  'Mary': 21,
}

map['John']; // 25

类型K可以是字符串类型或数值类型,而V可以是任何类型。

interface PersonInfo {
  age: number;
  salary: number;
}
let map: Record<string, PersonInfo> = {
  'John': { age: 25, salary: 10},
  'Mary': { age: 21, salary: 20}
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐