第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}!`;
你看,连function和return都省了。但这里有个坑,我踩过好几次——箭头函数没有自己的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}`;
}
}
这里我用了private和public来控制访问权限。我个人习惯把不需要外部访问的字段设为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接口,就必须提供save、load、clear这三个方法。如果漏了一个,编译器会直接报错。这就是契约的力量。
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的异步编程,那可是处理网络请求和文件读写的关键。到时候见!