本文通过提供简洁的约束,将标准的TypeScript代码重构为ArkTS代码。 尽管ArkTS是基于TypeScript设计的,但出于性能考虑,一些TypeScript的特性被限制了。因此,在ArkTS中,所有的TypeScript特性被分成三类。
- 完全支持的特性:原始代码无需任何修改。根据测试,对于已遵循最佳TypeScript实践的项目,代码库中90%到97%的内容可以保持原封不动。
- 部分支持的特性:需小规模的重构代码。例如,必须使用关键字let代替var来声明变量。
- 不支持的特性:需大规模的重构代码。例如,不支持any类型,所有使用any的代码都需要引入显式类型。
本文将介绍所有部分支持和所有不支持的特性,并提供重构代码的建议。根据本文提供的约束进行代码重构后代码仍为有效的TypeScript代码。对于没有提到的特性,则说明ArkTS完全支持。
示例
包含关键字var的原始TypeScript代码:
function addTen(x: number): number {
var ten = 10;
return x + ten;
}
重构后的代码:
function addTen(x: number): number {
let ten = 10;
return x + ten;
}
级别
约束分为两个级别:错误、警告。
- 错误: 必须要遵从的约束。如果不遵从该约束,将会导致程序编译失败。
- 警告: 推荐遵从的约束。尽管现在违反该约束不会影响编译流程,但是在将来,违反该约束可能将会导致程序编译失败。
不支持的特性
目前,不支持的特性主要包括:
- 与降低运行时性能的动态类型相关的特性。
- 需要编译器额外支持从而导致项目构建时间增加的特性。
根据开发者的反馈以及更多实际场景的数据,我们将来可能进一步缩小不支持特性的范围。
概述
本节罗列了ArkTS不支持或部分支持的TypeScript特性。
强制使用静态类型
ArkTS在设计之初,就确定了如下目标:
- 因为代码的阅读频率高于编写频率,ArkTS代码需非常容易阅读和理解。
- 以最小功耗快速执行代码,这点对于移动设备(ArkTS的目标设备)来说至关重要。
静态类型是ArkTS最重要的特性之一。使用静态类型有助于实现上述两个目标。如果程序采用静态类型,即所有类型在编译时都是已知的,那么开发者就能够容易理解代码中使用了哪些数据结构。同时,由于所有类型在程序实际运行前都是已知的,编译器可以提前验证代码的正确性,从而可以减少运行时的类型检查,有助于提升性能。
基于上述考虑,ArkTS中禁止使用any类型。
示例
// 不支持:
let res: any = some_api_function('hello', 'world');
// `res`是什么?错误代码的数字?字符串?对象?
// 该如何处理它?
// 支持:
class CallResult {
public succeeded(): boolean { ... }
public errorMessage(): string { ... }
}
let res: CallResult = some_api_function('hello', 'world');
if (!res.succeeded()) {
console.log('Call failed: ' + res.errorMessage());
}
any类型在TypeScript中并不常见,只有大约1%的TypeScript代码库使用。一些代码检查工具(例如ESLint)也制定一系列规则来禁止使用any。因此,虽然禁止any将导致代码重构,但重构量很小,有助于整体性能提升。
禁止在运行时变更对象布局
为实现最佳性能,ArkTS要求在程序执行期间不能更改对象的布局。换句话说,ArkTS禁止以下行为:
- 向对象中添加新的属性或方法。
- 从对象中删除已有的属性或方法。
- 将任意类型的值赋值给对象属性。
TypeScript编译器已经禁止了许多此类操作。然而,有些操作还是有可能绕过编译器的,例如,使用ArkTS不支持的as any进行转换。
示例
class Point {
public x: number = 0
public y: number = 0
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
// 无法从对象中删除某个属性,从而确保所有Point对象都具有属性x
let p1 = new Point(1.0, 1.0);
delete p1.x; // 在TypeScript和ArkTS中,都会产生编译时错误
delete (p1 as any).x; // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
// Point类没有定义命名为z的属性,在程序运行时也无法添加该属性
let p2 = new Point(2.0, 2.0);
p2.z = 'Label'; // 在TypeScript和ArkTS中,都会产生编译时错误
(p2 as any).z = 'Label'; // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
// 类的定义确保了所有Point对象只有属性x和y,并且无法被添加其他属性
let p3 = new Point(3.0, 3.0);
let prop = Symbol(); // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
(p3 as any)[prop] = p3.x; // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
p3[prop] = p3.x; // 在TypeScript和ArkTS中,都会产生编译时错误
// 类的定义确保了所有Point对象的属性x和y都具有number类型,因此,无法将其他类型的值赋值给它们
let p4 = new Point(4.0, 4.0);
p4.x = 'Hello!'; // 在TypeScript和ArkTS中,都会产生编译时错误
(p4 as any).x = 'Hello!'; // 在TypeScript中不会报错;在ArkTS中会产生编译时错误
// 使用符合类定义的Point对象:
function distance(p1: Point, p2: Point): number {
return Math.sqrt(
(p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y)
);
}
let p5 = new Point(5.0, 5.0);
let p6 = new Point(6.0, 6.0);
console.log('Distance between p5 and p6: ' + distance(p5, p6));
修改对象布局会影响代码的可读性以及运行时性能。从开发者的角度来说,在某处定义类,然后又在其他地方修改实际的对象布局,很容易引起困惑乃至引入错误。此外,这点还需要额外的运行时支持,增加了执行开销。这一点与静态类型的约束也冲突:既然已决定使用显式类型,为什么还需要添加或删除属性呢?
当前,只有少数项目允许在运行时变更对象布局,一些常用的代码检查工具也增加了相应的限制规则。这个约束只会导致少量代码重构,但会提升性能。
限制运算符的语义
为获得更好的性能并鼓励开发者编写更清晰的代码,ArkTS限制了一些运算符的语义。
示例
// 一元运算符`+`只能作用于数值类型:
let t = +42; // 合法运算
let s = +'42'; // 编译时错误
使用额外的语义重载语言运算符会增加语言规范的复杂度,而且,开发者还被迫牢记所有可能的例外情况及对应的处理规则。在某些情况下,产生一些不必要的运行时开销。
当前只有不到1%的代码库使用该特性。因此,尽管限制运算符的语义需要重构代码,但重构量很小且非常容易操作,并且,通过重构能使代码更清晰、具备更高性能。
不支持 structural typing
假设两个不相关的类T和U拥有相同的publicAPI:
class T {
public name: string = ''
public greet(): void {
console.log('Hello, ' + this.name);
}
}
class U {
public name: string = ''
public greet(): void {
console.log('Greetings, ' + this.name);
}
}
能把类型为T的值赋给类型为U的变量吗?
let u: U = new T(); // 是否允许?
能把类型为T的值传递给接受类型为U的参数的函数吗?
function greeter(u: U) {
console.log('To ' + u.name);
u.greet();
}
let t: T = new T();
greeter(t); // 是否允许?
换句话说,我们将采取下面哪种方法呢:
- T和U没有继承关系或没有implements相同的接口,但由于它们具有相同的publicAPI,它们“在某种程度上是相等的”,所以上述两个问题的答案都是“是”;
- T和U没有继承关系或没有implements相同的接口,应当始终被视为完全不同的类型,因此上述两个问题的答案都是“否”。
采用第一种方法的语言支持structural typing,而采用第二种方法的语言则不支持structural typing。目前TypeScript支持structural typing,而ArkTS不支持。
structural typing是否有助于生成清晰、易理解的代码,关于这一点并没有定论。那为什么ArkTS不支持structural typing呢?
因为对structural typing的支持是一个重大的特性,需要在语言规范、编译器和运行时进行大量的考虑和仔细的实现。另外,由于ArkTS使用静态类型,运行时为了支持这个特性需要额外的性能开销。
鉴于此,当前我们还不支持该特性。根据实际场景的需求和反馈,我们后续会重新加以考虑。
那么要想成为一名鸿蒙高级开发,以上知识点是必须要掌握的,除此之外,还需要掌握一些鸿蒙应用开发相关的一些技术,需要我们共同去探索。
为了节省大家一些查找的时间,这边联合几位行业大佬,为大家准备了一份《OpenHarmony4.0&Next》的学习导图,从入门到进阶再到南北向开发实战的一整套完整体系,想要学习了解更多鸿蒙开发的相关知识可以借鉴:《做鸿蒙应用开发到底学习些啥?》
除了上面整理的思维导图以外,这里还特别整理的一份《鸿蒙 (Harmony OS)开发学习手册》给大家进行参考学习:
一、入门必看
1. 应用开发导读(ArkTS)
2. ……
二、HarmonyOS 概念
1. 系统定义
2. 技术架构
3. 技术特性
4. 系统安全
5........
三、如何快速入门?《鸿蒙基础入门开发宝典!》
1. 基本概念
2. 构建第一个ArkTS应用
3. 构建第一个JS应用
4. ……
四、开发基础知识
1. 应用基础知识
2. 配置文件
3. 应用数据管理
4. 应用安全管理
5. 应用隐私保护
6. 三方应用调用管控机制
7. 资源分类与访问
8. 学习ArkTS语言
9. ……
五、基于ArkTS 开发
1. Ability开发
2. UI开发
3. 公共事件与通知
4. 窗口管理
5. 媒体
6. 安全
7. 网络与链接
8. 电话服务
9. 数据管理
10. 后台任务(Background Task)管理
11. 设备管理
12. 设备使用信息统计
13. DFX
14. 国际化开发
15. 折叠屏系列
16. ……
更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙开发学习指南》