• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# ArkTS语法适配背景
2
3<!--Kit: ArkTS-->
4<!--Subsystem: ArkCompiler-->
5<!--Owner: @Fouckttt1-->
6<!--Designer: @qyhuo32-->
7<!--Tester: @kirl75; @zsw_zhushiwei-->
8<!--Adviser: @zhang_yixin13-->
9
10ArkTS在保留TypeScript(简称TS)基本语法风格的基础上,进一步通过规范强化了静态检查和分析,使得开发者在程序开发阶段能够检测出更多错误,提升程序的稳定性和运行性能。本文将详细解释为什么建议将TS代码适配为ArkTS代码。
11
12## 程序稳定性
13
14动态类型语言如JavaScript(简称JS)虽能提升开发效率,但也容易在运行时引发非预期错误。例如未检查的`undefined`值可能导致程序崩溃,这类问题若能在开发阶段发现将显著提升稳定性。TypeScript(TS)通过类型标注机制,使编译器能在编译时检测出多数类型错误,但其非强制类型系统仍存在局限。例如未标注类型的变量会阻碍完整编译检查。ArkTS通过强制静态类型系统克服这一缺陷,实施更严格的类型验证机制,从而最大限度减少运行时错误的发生。
15
16下面这个例子展示了ArkTS通过强制严格的类型检查来提高代码稳定性和正确性。
17
18
19**显式初始化类的属性**
20
21ArkTS要求类的所有属性在声明时或者在构造函数中显式地初始化,这和TS中的`strictPropertyInitialization`检查一致。以下的代码片段是非严格模式下的TS代码。
22
23```typescript
24class Person {
25  name: string; // undefined
26
27  setName(n: string): void {
28    this.name = n;
29  }
30
31  getName(): string {
32  // 开发者使用"string"作为返回类型,这隐藏了name可能为"undefined"的事实。
33  // 更合适的做法是将返回类型标注为"string | undefined",以告诉开发者这个API所有可能的返回值的类型。
34    return this.name;
35  }
36}
37
38let buddy = new Person()
39// 假设代码中没有对name的赋值,例如没有调用"buddy.setName('John')"
40buddy.getName().length; // 运行时异常:name is undefined
41```
42
43ArkTS要求属性显式初始化,代码应如下所示:
44
45```typescript
46class Person {
47  name: string = '';
48
49  setName(n: string): void {
50    this.name = n;
51  }
52
53  // 类型为"string",不可能为"null"或者"undefined"
54  getName(): string {
55    return this.name;
56  }
57}
58
59let buddy = new Person()
60// 假设代码中没有对name的赋值,例如没有调用"buddy.setName('John')"
61buddy.getName().length; // 0, 没有运行时异常
62```
63
64如果`name`可以是`undefined`,其类型应在代码中精确标注。
65
66```typescript
67class Person {
68    name?: string; // 可能为undefined
69
70    setName(n: string): void {
71        this.name = n;
72    }
73
74    // 编译时错误:name可能为"undefined",所以不能将这个API的返回类型标注为"string"
75    getNameWrong(): string {
76        return this.name;
77    }
78
79    getName(): string | undefined { // 返回类型匹配name的类型
80        return this.name;
81    }
82}
83
84let buddy = new Person()
85// 假设代码中没有对name的赋值,例如没有调用"buddy.setName('John')"
86
87// 编译时错误:编译器认为下一行代码有可能访问"undefined"的属性,报错
88buddy.getName().length;  // 编译失败
89
90buddy.getName()?.length; // 编译成功,没有运行时错误
91```
92
93## 程序性能
94
95为了确保程序的正确性,动态类型语言需要在运行时检查对象的类型。例如JavaScript不允许访问`undefined`的属性。检查一个值是否为`undefined`的唯一方法是在运行时进行类型检查。所有JavaScript引擎都会执行以下操作:如果一个值不是`undefined`,则可以访问其属性;如果尝试访问的值是`undefined`,则会抛出异常。虽然现代JavaScript引擎可以优化这类操作,但仍然存在一些无法消除的运行时检查,这会导致程序变慢。由于TypeScript代码总是先被编译成JavaScript代码,因此在TypeScript中也会遇到相同的问题。ArkTS解决了这个问题。通过启用静态类型检查,ArkTS代码将被编译成方舟字节码文件,而不是JavaScript代码。因此,ArkTS运行速度更快,更容易被进一步优化。
96
97
98**Null Safety**
99
100```typescript
101function notify(who: string, what: string) {
102  console.info(`Dear ${who}, a message for you: ${what}`);
103}
104
105notify('Jack', 'You look great today');
106```
107
108在大多数情况下,函数`notify`会接受两个`string`类型的变量作为输入,产生一个新的字符串。但是,如果将一些特殊值作为输入,例如`notify(null, undefined)`,情况会怎么样呢?
109程序仍会正常运行,输出预期值:`Dear null, a message for you: undefined`。虽然系统表现一切正常,但值得注意的是,为了保障该场景下程序的正确性,引擎在运行时会持续进行类型检查,其实现机制类似于以下伪代码所示:
110
111```typescript
112function __internal_tostring(s: any): string {
113  if (typeof s === 'string')
114    return s;
115  if (s === undefined)
116    return 'undefined';
117  if (s === null)
118    return 'null';
119  // ...
120}
121```
122
123试想一下,如果`notify`函数并非只是简单的日志打印,而是某些高负载场景下关键逻辑的一部分,那么在运行时频繁执行类似`__internal_tostring`的类型检查操作,势必会带来显著的性能开销。
124
125如果可以保证在运行时,只有`string`类型的值(不会是其他类型的值,例如`null`或者`undefined`)可以被传入函数`notify`呢?在这种情况下,因为可以确保没有其他边界情况,像`__internal_tostring`的检查就是多余的了。在该场景下,这种机制被称为“null-safety”(空安全),其核心目的是确保`null`不能作为合法的字符串类型值。如果ArkTS支持这一特性,那么任何类型不匹配的代码都将在编译阶段被拦截,无法编译通过。
126
127```typescript
128function notify(who: string, what: string) {
129  console.info(`Dear ${who}, a message for you: ${what}`);
130}
131
132notify('Jack', 'You look great today');
133notify(null, undefined); // 编译时错误
134```
135
136TS通过启用编译选项`strictNullChecks`实现此特性。虽然TS被编译成JS,但因为JS没有这个特性,所以严格`null`检查仅在编译时起效。从程序稳定性和性能的角度考虑,ArkTS将“null-safety”视为一个重要的特性。因此,ArkTS强制进行严格`null`检查,在ArkTS中上述代码将会编译失败。作为交换,此类代码为ArkTS引擎提供了更多信息和关于值的类型保证,有助于优化性能。
137
138
139## .ets代码兼容性
140
141在API version 10之前的版本中,ArkTS(以.ets为扩展名的文件)在语法层面完全遵循标准的TypeScript规范。从API version 10 Release起,明确定义ArkTS的语法规则,同时,SDK增加了在编译流程中对.ets文件的ArkTS语法检查,通过编译告警或编译失败提示开发者适配新的ArkTS语法。
142
143根据工程的compatibleSdkVersion,具体策略如下:
144
145  - compatibleSdkVersion >= 10 为标准模式。在该模式下,所有.ets文件必须严格遵循ArkTS语法规则,任何语法违规工程都会编译不通过,开发者需要修正所有语法问题后才能获得编译通过。
146  - compatibleSdkVersion < 10 为兼容模式。在该模式下,对.ets文件以warning形式提示违反ArkTS语法规则的所有代码。尽管违反ArkTS语法规则的工程在兼容模式下仍可编译成功,但需完全适配ArkTS语法后方可在标准模式下编译成功。
147
148## 支持与TS/JS的交互
149
150ArkTS支持与TS/JS的高效互操作。在当前版本中,ArkTS运行时兼容动态类型对象语义。在ArkTS与TypeScript/JavaScript交互操作场景中,直接复用TS/JS的数据和对象作为ArkTS的实体使用时,可能规避ArkTS的静态类型检查机制,进而引发运行时异常或引入额外的性能损耗。
151
152```typescript
153// lib.ts
154export class C {
155  v: string; // 编译期报错 Property 'v' has no initializer
156}
157
158export let c = new C()
159
160// app.ets
161import { C, c } from './lib';
162
163function foo(c: C) {
164  c.v.length;
165}
166
167foo(c);
168```
169
170## 方舟运行时兼容TS/JS
171
172在API version 11上,OpenHarmony SDK中的TypeScript版本为4.9.5,target字段为es2017。应用中支持使用ECMA2017及更高版本的语法进行TS/JS开发。
173
174**应用环境限制**
175
1761. 强制使用严格模式(use strict)
1772. 禁止使用`eval()`
1783. 禁止使用`with() {}`
1794. 禁止以字符串为代码创建函数
1805. 禁止循环依赖
181
182    循环依赖示例:
183    ```typescript
184    // bar.ets
185    import {v} from './foo'; // bar.ets依赖foo.ets
186    export let u = 0;
187
188    // foo.ets
189    import {u} from './bar'; // foo.ets同时又依赖bar.ets
190    export let v = 0;
191
192    //应用加载失败
193    ```
194
195**与标准TS/JS的差异**
196
197在标准的TS/JS中,JSON的数字格式要求小数点后必须跟随数字,例如 `2.e3` 这类科学计数法不被允许,会导致 `SyntaxError`。方舟运行时则支持这类科学计数法。
198