前言

这是我的学习笔记。ArkTS的基础知识已经学习了一半,继续努力。之前的我只会一点C和C++语言,那么现在的我,又初步学习了一门新的语言。不过ArkTS和C语言还真不是一般的不一样!目前只能说是了解这门语言了。难,比C语言的双指针还抽象o(╥﹏╥)o。

 

让我们通过学习类,以类为突破口进行这一阶段的学习 。

类 

太抽象,直接上代码吧。(之后相当一部分内容都会围绕该段代码展开!

class Person {
    // 类的属性
    name: string;
    age: number;

    // 类的构造函数
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    // 类的方法
    sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

上述代码中定义了一个 person类,它有两个属性  name 和  age  ,一个构造函数constructor,和一个方法  sayhello  。

class关键字

用  class  关键字来定义一个类

类的属性(Property)

属性和字段的区分先放一放吧

实例属性

实例属性就是属于类的每个实例的变量。name  和  age  就是  实例属性,每个  person  类的实例都有自己独立的  name  和  age  值。

静态属性

使用  static  关键字定义,属于类的本身而不是类的实例。

可以通过类名直接访问静态属性,不需要创建类的实例。

举例:

class MathUtils {
    static PI: number = 3.14159;

    static calculateCircleArea(radius: number): number {
        return MathUtils.PI * radius * radius;
    }
}

 这里的  PI  就是静态属性,可以通过  MathUtils.PI  来访问。

类的方法

方法其实感觉跟函数差不多

实例方法

可以在类的实例上调用的函数,可以操作和访问实例的属性。

比如上面代码中  sayhello  方法,它可以访问  this.name  和  this.age  来获取实例的属性值。

静态方法

同样使用关键字  static  来定义,可以通过类名直接调用,但不能访问实例属性。因为它们不与类的特定实例相关联。

MathUtils类中的calculateCircleArea方法。

 

 

This关键字

 豆包给了一个有意思的解释(包懂),原文看链接:ArkTS 类的介绍 - 豆包咱们用生活中的例子来通俗地理解 this 关键字,就以开一家奶茶店来打比方吧。https://www.doubao.com/thread/w4c299d37cc16f662https://www.doubao.com/thread/w4c299d37cc16f662https://www.doubao.com/thread/w4c299d37cc16f662

 再来看这个代码:

// 定义一个 Student 类
class Student {
    // 类的属性,用于存储学生的姓名
    name: string;

    // 类的构造函数,用于初始化学生的姓名
    constructor(name: string) {
        // this 指向当前正在创建的对象实例
        // 将传入的 name 参数赋值给当前对象的 name 属性
        this.name = name;
    }

    // 类的方法,用于让学生打招呼
    sayHi() {
        // this 指向调用该方法的对象实例
        // 输出包含当前对象姓名的打招呼信息
        console.log(`Hi, my name is ${this.name}.`);
    }
}

// 创建一个 Student 类的实例,名为 "Alice"
const alice = new Student("Alice");
// 调用 alice 对象的 sayHi 方法
alice.sayHi();

// 创建另一个 Student 类的实例,名为 "Bob"
const bob = new Student("Bob");
// 调用 bob 对象的 sayHi 方法
bob.sayHi();

 这下理解了吧。

使用限制

  • 不支持this类型
  • 不支持在函数和类的静态方法中使用this
  • 关键字this只能在类的实例方法中使用。

官方的错误实例:

class A {
  n: number = 0;
  f1(arg1: this) {} // 编译时错误,不支持this类型
  static f2(arg1: number) {
    this.n = arg1;  // 编译时错误,不支持在类的静态方法中使用this
  }
}

function foo(arg1: number) {
  this.n = i;       // 编译时错误,不支持在函数中使用this
}

constructor  关键字 

constructor  关键字是配合  new  关键字来完成类的实例创建的。

关键字  constructor  定义构造函数。用来告诉  new  如何创建。

当 new  关键字创建一个类的实例时,会自动调用该类的构造函数,完成对象的初始化操作。

new关键字

根据该类的构造函数的指示创建实例。

还是那段代码:

class Person {
  name: string;
  age: number;

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

  sayHello() {
    console.log(`Hello, my name is ${this.name}, and I'm ${this.age} years old.`);
  }
}
// 使用 new 关键字创建 Person 类的实例
const person = new Person('Alice', 25);
person.sayHello(); 

 传入参数的类型和顺序必须一致。

 

类的继承

通过类的继承,一个类可以继承另一个类的属性和方法,从而实现代码的复用和扩展。

在这里使用  extend  关键字来实现继承

例如:

class Student extends Person {
    grade: number;

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

    sayHello(): void {
        console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old and I'm in grade ${this.grade}.`);
    }
}

这样 student  类就继承了  person 类,并拥有了他所有的属性和方法 ,同时又添加他自己的属性   grade  ,并重写了  sayhello  方法。

子类和父类

子类(也称为派生类)是基于另一个类(称为父类、基类或超类)创建出来的新类。子类继承了父类的属性和方法,同时还可以拥有自己独特的属性和方法,或者对父类的方法进行重写,从而实现代码的复用和功能的扩展。 

所以称  student  为  person  的子类,person  为  student  的父类。

  通过使用子类,可以构建出层次分明的类结构,提高代码的可维护性和可扩展性。每个子类可以专注于实现特定的功能,同时共享父类的通用功能。

 

super关键字

 

调用父类构造函数

在子类的构造函数中若要调用父类的构造函数,就要用到   super()。 

 在 ArkTS 里,当子类定义构造函数时,必须在子类构造函数的第一行调用 super()(如果父类有构造函数的话)。

即:super() 必须在使用 this 之前调用,因为 super() 负责初始化从父类继承的属性,如果不先调用 super()this 可能处于未正确初始化的状态。 

调用父类的方法

当子类已经重写了父类的某个方法时,若又想调用父类的原始方法时,就可以使用  super()  。

示例

又是那段代码。

// 定义父类 Person
class Person {
    // 类的属性
    name: string;
    age: number;
    // 类的构造函数
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    // 类的方法
    sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}
// 定义子类 Employee,继承自 Person
class Employee extends Person {
    // 子类新增的属性
    jobTitle: string;
    // 子类的构造函数
    constructor(name: string, age: number, jobTitle: string) {
        // 调用父类的构造函数
        super(name, age);
        this.jobTitle = jobTitle;
    }
    // 重写父类的 sayHello 方法
    sayHello(): void {
        // 调用父类的 sayHello 方法
        super.sayHello();
        console.log(`I work as a ${this.jobTitle}.`);
    }
}
// 创建 Employee 类的实例
const employee = new Employee('Alice', 28, 'Software Engineer');
// 调用重写后的 sayHello 方法
employee.sayHello();

 

访问和调用

其实很简单,但起的这名字...... 

访问(Access)

访问主要是针对类的属性而言的,指的是获取或修改类的属性值。

可以将类的属性看作是类所拥有的数据,访问就是对这些数据进行操作的过程。

还是根据那段代码

class Person {
    public name: string;
    private age: number;

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

const person = new Person("Alice", 25);
// 访问 public 属性并获取其值
const personName = person.name;
console.log(personName); 

// 尝试访问 private 属性(会报错)
// const personAge = person.age; 

在上述代码中,person.name 就是对 Person 类实例 person 的 name 属性进行访问以获取其值。person.name = "Bob"; 就是对 name 属性进行访问并修改其值。

需要注意的是,属性的访问可能会受到访问修饰符(如 publicprivateprotected)的限制。像 age 是 private 属性,在类外部无法直接访问。

调用(Invoke)

调用主要是针对类的方法而言,指的是执行类中定义的方法。

方法是类所具备的行为,调用是指触发这些行为的过程。

class Calculator {
    add(a: number, b: number): number {
        return a + b;
    }
}

const calculator = new Calculator();
// 调用 add 方法
const result = calculator.add(3, 5);
console.log(result); 

 在上述代码中,calculator.add(3, 5) 就是对 Calculator 类实例 calculator 的 add 方法进行调用。

 

访问修饰符

public

公共访问修饰符,也是默认修饰符。

被  public  修饰的属性或方法可以在类的内部和外部被自由的访问和调用。

private

私有访问修饰符。

被  private  修饰的属性或方法只能在类的内部被访问和调用,外部无法直接访问。

protected

受保护的访问修饰符。

被  protected  修饰的属性或方法可以在类的内部以及子类中被访问,外部无法直接访问。

 

字段  (Field)

到这里就不得不区分<字段>和<属性>了。

先看看字段

字段(Field)

字段是类中用于存储数据的基本单元。在 ArkTS 中,字段可以简单理解为类里声明的变量。

它们直接存储了对象的相关数据 。

class Person {
    name: string;
    age: number;

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

    sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

 其实严格的说,name,age是字段。

 所以,是这样的:

class Person {
    // 字段声明
    name: string;
    age: number;

    // 构造函数,用于初始化字段
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    // 类的方法,使用字段
    sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

// 创建 Person 类的实例
const person = new Person('John', 25);
// 直接访问字段
console.log(person.name); 
console.log(person.age);   
person.sayHello();

 

 属性(Property)和getter,setter

属性是对字段的一种封装,通常会包含 getter(取值方法)和 setter(赋值方法),可以在访问或修改数据时添加额外的逻辑。

class PersonWithProperty {
    // 私有字段,外部不能直接访问
    private _name: string;
    private _age: number;

    // name 属性的 getter 方法
    get name(): string {
        return this._name;
    }

    // name 属性的 setter 方法
    set name(value: string) {
        if (value.length > 0) {//名字的长度大于一
            this._name = value;
        } else {
            console.log('Name cannot be empty.');
        }
    }

    // age 属性的 getter 方法
    get age(): number {
        return this._age;
    }

    // age 属性的 setter 方法
    set age(value: number) {
        if (value >= 0 && value <= 150) {//年龄在0到150之间
            this._age = value;
        } else {
            console.log('Invalid age. Age should be between 0 and 120.');
        }
    }

    // 构造函数
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    // 类的方法,使用属性
    sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}

// 创建 PersonWithProperty 类的实例
const personWithProp = new PersonWithProperty('Alice', 30);
// 通过属性访问数据
console.log(personWithProp.name); 
// 通过属性修改数据
personWithProp.age = 35; 
console.log(personWithProp.age);   
personWithProp.sayHello();

在这个例子中,_name 和 _age 是私有字段,外部无法直接访问。而 name 和 age 是属性,它们通过 getter 和 setter 方法对私有字段进行封装。在 setter 方法中添加了数据验证逻辑,确保输入的数据是有效的。

方法签名

方法签名用于唯一标识一个方法,由方法名和参数列表组成。(不包括返回类型)

再看: 

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

构造函数也可以认为是一种方法。

constructor  是方法名;(name: string, age: number) 是参数列表。

再看

 sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }

 方法名是  sayhello  ;参数列表是  ()  ;就是参数列表为空。

void  是返回类型,不属于方法签名。

 

方法重载

允许一个类中定义多个同名的方法,但这些方法的参数列表不同。(就像函数重载) 

当一个类中有多个同名的方法,但它们的方法重载签名不同时,就构成了方法重载。

正确示例

class Calculator {
    // 方法重载签名 1:接收两个 number 类型的参数
    add(a: number, b: number): number;
    // 方法重载签名 2:接收两个 string 类型的参数
    add(a: string, b: string): string;

    // 实际的实现方法
    add(a: number | string, b: number | string): number | string {
        if (typeof a === 'number' && typeof b === 'number') {
            return a + b;
        } else if (typeof a === 'string' && typeof b === 'string') {
            return a + b;
        }
        throw new Error('Unsupported parameter types');
// throw 抛出异常
    }
}

const calculator = new Calculator();
// 调用第一个重载方法
const numResult = calculator.add(1, 2);
console.log(numResult); // 输出 3

// 调用第二个重载方法
const strResult = calculator.add('Hello, ', 'World!');
console.log(strResult); // 输出  Hello,World!

白菜头错误示例

class Example {
    // 错误:不能仅根据返回类型区分重载方法
    doSomething(): number;
    doSomething(): string;
    doSomething(): number | string {
        return Math.random() > 0.5 ? 1 : 'test';
    }
}

返回类型不是方法签名的一部分:在定义方法重载时,返回类型可以不同,但不能仅根据返回类型来区分重载方法。

实现方法的参数类型要兼容所有重载签名:实现方法的参数类型必须能够涵盖所有重载签名的参数类型,通常使用联合类型来实现。

抽象类

是一种不能被实例化的类,主要用于作为其他类的基类(父类),为子类提供一个通用的模板。

子类可以继承抽象类,并实现其中的抽象方法。

abstract关键字

有  abstract  修饰符修饰的类称之为抽象类。

有  abstract  修饰符修饰的方法称之为抽象方法。

抽象方法

但是只有抽象类中才能有抽象方法。

抽象方法是一种只有方法签名,而没有具体实现的方法,必须在抽象类中定义。

示例

把最初的代码改一下:

// 定义抽象类 Person
abstract class Person {
    // 类的属性
    name: string;
    age: number;
    // 类的构造函数
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
    // 抽象方法,没有具体实现
    abstract introduce(): void;
    // 普通方法
    sayHello(): void {
        console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
    }
}
// 定义子类 Student,继承自抽象类 Person
class Student extends Person {
    // 子类新增的属性
    major: string;
    // 子类的构造函数
    constructor(name: string, age: number, major: string) {
        // 调用父类的构造函数
        super(name, age);
        this.major = major;
    }
    // 实现抽象类中的抽象方法
    introduce(): void {
        console.log(`I'm ${this.name}, ${this.age} years old, and I'm majoring in ${this.major}.`);
    }
}

// 尝试实例化抽象类(这会报错)
// const person = new Person('Alice', 25); 

// 创建 Student 类的实例
const student = new Student('Bob', 20, 'Computer Science');
student.sayHello(); 
student.introduce(); 
  • 使用 abstract 关键字将 Person 类定义为抽象类。这意味着不能直接使用 new Person() 来创建 Person 类的实例。
  • introduce 方法被定义为抽象方法,它只有方法签名,没有具体的实现代码。抽象方法的存在是为了让子类去实现具体的逻辑。
  • Student 类继承自抽象类 Person由于 Person 类中有抽象方法 introduce,所以 Student 类必须实现这个抽象方法,否则 Student 类也需要被声明为抽象类。
  • 在 Student 类中实现了 introduce 方法,提供了具体的输出逻辑。
  • 调用 sayHello 方法,这是 Person 类中定义的普通方法;调用 introduce 方法,这是在 Student 类中实现的抽象方法。

可以说抽象类为子类也提供了一种规范。

 

对象字面量

其实感觉和类差不多,比类更简单一点,是那种直接赋值,直接用的

基本语法

对象字面量是使用花括号 {} 直接创建对象的一种语法形式,通过键值对来定义对象的属性和方法。

 键与值之间用  :  分隔;键值对之间用 分隔。

使用 或  [ ]  来访问对象的属性。

还是利用那段代码:

// 创建一个对象字面量
const person = {
    name: 'Alice',
// 键为 name    值为  Alice
    age: 25,
// 键为 age     值为  25
    sayHello() {
        console.log(`Hello, my name is ${this.name}.`);
    }
};

// 调用对象的方法
person.sayHello(); 

与类很像吧,再对比一下:

class Person {
    name: string;
    age: number;

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

    sayHello() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

// 创建类的实例并初始化属性
const personInstance = new Person('Bob', 30);
personInstance.sayHello(); 

对象字面量在某方面更方便,可以替代  new表达式

  • 对象字面量是一种轻量级的创建对象的方式,适合快速创建简单的对象,不需要预先定义类的结构。
  • 类是一种面向对象编程的概念,用于定义对象的结构和行为模板。当需要创建多个具有相同结构和行为的对象时,使用类更合适,它可以实现代码的复用和封装。
  • 类可以通过继承来扩展和修改父类的行为,实现多态性,提供更强大的代码组织和扩展能力
  • 对象字面量本身不支持继承和多态等面向对象的特性。

record  类型

Record<Keys,Type>

Keys,表示对象键的类型;

Type,表示对象值的类型;

record类型的作用是创建一个对象类型,该对象所有键都属于keys类型,所有值都属于type类型。

使用record类型定义对象结构 

// 定义一个 Record 类型,键为字符串类型,值为数字类型
type ScoreRecord = Record<string, number>;

// 创建一个符合 ScoreRecord 类型的对象字面量
const scores: ScoreRecord = {
    "Alice": 85,
    "Bob": 92,
    "Charlie": 78
};

console.log(scores); 

使用联合类型作为键

这意味着对象可以有不同的键。

// 定义一个联合类型作为键
type Weekday = 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday';

// 定义一个 Record 类型,键为 Weekday 类型,值为字符串类型
type TaskRecord = Record<Weekday, string>;

// 创建一个符合 TaskRecord 类型的对象字面量
const tasks: TaskRecord = {
    Monday: 'Meeting',
    Tuesday: 'Coding',
    Wednesday: 'Testing',
    Thursday: 'Documentation',
    Friday: 'Review'
};

console.log(tasks); 

输出为

{

Monday: 'Meeting',

Tuesday: 'Coding',

Wednesday: 'Testing',

Thursday: 'Documentation',

Friday: 'Review'

}

 

还有《接口》《模块》《空安全》。

下期吧

 

Logo

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

更多推荐