第4章 ArkTS语言基础(下):函数与面向对象编程

好,咱们接着聊。上一章我们把ArkTS的基础类型和变量讲透了,这一章我带你深入函数和面向对象。说实话,这两个东西是日常开发里用得最频繁的。你写一个页面,几乎每一行都在跟函数打交道;你封装一个组件,本质上就是在做面向对象的事情。

4.1 函数:不止是function

函数在ArkTS里,其实比你想的要灵活得多。我刚开始从Java转过来时,还不太习惯这种“函数是一等公民”的玩法。后来写多了才发现,真香。

4.1.1 函数声明与表达式

最基本的写法,你肯定见过:

function greet(name: string): string {
    return `你好,${name}!`;
}

但ArkTS还支持函数表达式,说白了就是把函数赋值给一个变量:

let greet = function(name: string): string {
    return `你好,${name}!`;
};

这两种写法有啥区别?我个人习惯用第一种,因为更直观。但有些场景下,函数表达式更灵活,比如作为回调函数传递时。

4.1.2 箭头函数:简洁到极致

箭头函数是ES6引入的,ArkTS完全支持。它的语法非常简洁:

let greet = (name: string): string => `你好,${name}!`;

你看,连functionreturn都省了。但这里有个坑,我踩过好几次——箭头函数没有自己的this。什么意思呢?

我曾经在写一个按钮点击事件时,用了普通函数,结果this指向了按钮本身,而不是我期望的组件实例。改成箭头函数后,问题就解决了。
// 普通函数:this指向调用者
button.onClick(function() {
    console.log(this); // 指向button
});

// 箭头函数:this指向定义时的上下文
button.onClick(() => {
    console.log(this); // 指向组件实例
});

4.1.3 高阶函数:函数也能当参数

高阶函数,说白了就是“吃函数的函数”。它要么接收函数作为参数,要么返回一个函数。你想想看,这玩意儿在数据处理时有多好用。

举个例子,数组的map方法就是一个典型的高阶函数:

let numbers: number[] = [1, 2, 3, 4, 5];
let doubled = numbers.map((n) => n * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

我在项目中遇到过这样一个场景:需要对用户列表做各种筛选和排序。如果不用高阶函数,你得写一堆重复的循环代码。用了高阶函数,代码瞬间清爽了:

function filterAndSort(
    list: User[],
    filterFn: (user: User) => boolean,
    sortFn: (a: User, b: User) => number
): User[] {
    return list.filter(filterFn).sort(sortFn);
}

// 调用时传入不同的函数
let activeUsers = filterAndSort(
    users,
    (user) => user.isActive,
    (a, b) => a.age - b.age
);

4.2 面向对象编程:从类到接口

面向对象这块,ArkTS跟TypeScript基本一致。如果你有Java或C++基础,上手会很快。但有些细节,嗯,还是值得注意的。

4.2.1 类:万物皆对象

定义一个类,其实就是在描述一类事物的属性和行为。比如我们做一个待办事项应用:

class TodoItem {
    private id: number;
    public title: string;
    public completed: boolean = false;

    constructor(id: number, title: string) {
        this.id = id;
        this.title = title;
    }

    public toggle(): void {
        this.completed = !this.completed;
    }

    public getSummary(): string {
        return `[${this.completed ? '✓' : ' '}] ${this.title}`;
    }
}

这里我用了privatepublic来控制访问权限。我个人习惯把不需要外部访问的字段设为private,这样能减少耦合。

4.2.2 继承:站在巨人的肩膀上

继承是面向对象的核心特性之一。子类可以复用父类的代码,也可以扩展自己的功能。

class UrgentTodoItem extends TodoItem {
    public deadline: Date;

    constructor(id: number, title: string, deadline: Date) {
        super(id, title); // 调用父类构造函数
        this.deadline = deadline;
    }

    // 重写父类方法
    public getSummary(): string {
        let base = super.getSummary();
        return `${base} (截止: ${this.deadline.toLocaleDateString()})`;
    }
}

你注意看,子类里用super调用了父类的构造函数和方法。这个super关键字,说白了就是“爸爸,帮我干点活”。

避坑指南:子类构造函数里,必须先调用super(),然后才能访问this。否则会报错。我曾经因为这个bug查了半小时...

4.2.3 接口:契约的力量

接口定义了一组规范,任何实现它的类都必须遵守。这玩意儿在团队协作时特别有用。

interface IStorage {
    save(data: string): void;
    load(): string | null;
    clear(): void;
}

class LocalStorage implements IStorage {
    private key: string;

    constructor(key: string) {
        this.key = key;
    }

    save(data: string): void {
        localStorage.setItem(this.key, data);
    }

    load(): string | null {
        return localStorage.getItem(this.key);
    }

    clear(): void {
        localStorage.removeItem(this.key);
    }
}

你看,LocalStorage类实现了IStorage接口,就必须提供saveloadclear这三个方法。如果漏了一个,编译器会直接报错。这就是契约的力量。

4.2.4 枚举:有限的选择

枚举用来表示一组有限的常量。比如任务的状态:

enum TaskStatus {
    PENDING = 'pending',
    IN_PROGRESS = 'in_progress',
    COMPLETED = 'completed',
    CANCELLED = 'cancelled'
}

class Task {
    public status: TaskStatus = TaskStatus.PENDING;

    public start(): void {
        this.status = TaskStatus.IN_PROGRESS;
    }

    public complete(): void {
        this.status = TaskStatus.COMPLETED;
    }
}

用枚举的好处是,你不可能给status赋一个奇怪的值。比如你写task.status = 'done',编译器会直接告诉你:不行,只能用枚举里定义的值。

核心总结:

  • 函数可以赋值给变量,箭头函数没有自己的this
  • 高阶函数让代码更简洁、更灵活
  • 类封装了数据和行为,继承实现了代码复用
  • 接口定义了规范,枚举限制了取值范围

好了,这一章的内容就到这里。下一章我们会讲ArkTS的异步编程,那可是处理网络请求和文件读写的关键。到时候见!